diff --git a/ArchUnitNET/Domain/PlantUml/Export/GenerationOptions.cs b/ArchUnitNET/Domain/PlantUml/Export/GenerationOptions.cs index 44348f502..6687cb14f 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/GenerationOptions.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/GenerationOptions.cs @@ -10,5 +10,14 @@ public class GenerationOptions public bool IncludeNodesWithoutDependencies { get; set; } = true; public bool LimitDependencies { get; set; } = false; public bool C4Style { get; set; } = false; + + /// + /// Whether slice diagrams draw the namespace tree as package blocks and put each + /// slice in the package of the deepest namespace that contains all of its types. A package + /// with no slice of its own and a single child package is merged with it. A slice whose + /// types share no namespace is drawn outside any package. Slices of the obsolete + /// MatchingWithPackages are always nested. + /// + public bool NestSlicesInPackages { get; set; } = false; } } diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDependency.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDependency.cs index 431b8cee0..e5cfd02e4 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDependency.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDependency.cs @@ -81,16 +81,7 @@ public string GetPlantUmlString(RenderOptions renderOptions = null) return OriginAlias + " -[#green]> " + TargetAlias + Environment.NewLine; case DependencyType.OneToOneCompact: - if (OriginCountOfDots() == TargetCountOfDots()) - { - return "[" - + OriginAlias - + "] --> [" - + TargetAlias - + "]" - + Environment.NewLine; - } - return ""; + return "[" + OriginAlias + "] --> [" + TargetAlias + "]" + Environment.NewLine; case DependencyType.Circle: return "[" diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs index 7d0e01167..1412fd343 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs @@ -64,6 +64,10 @@ public string GetPlantUmlString(RenderOptions renderOptions) /// 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. + /// + /// A valid name can still be taken by a package: a nested slice named after its own + /// namespace, such as "App.Orders", would share its id with the package "App.Orders" it + /// is drawn in. Such a slice gets a replacement id as well. /// private static void AssignAliases(IReadOnlyCollection elements) { @@ -72,13 +76,22 @@ private static void AssignAliases(IReadOnlyCollection elements // Only slices are declared with an id; a dependency between components that are not // declared names them in brackets, where PlantUML accepts more. var sliceNames = slices.Select(slice => slice.ToString()).Distinct().ToList(); + var packageIds = new HashSet( + slices.Where(slice => slice.IsNested).SelectMany(slice => slice.PackageIds) + ); var taken = new HashSet( - sliceNames.Concat(dependencies.SelectMany(dep => new[] { dep.Origin, dep.Target })) + sliceNames + .Concat(dependencies.SelectMany(dep => new[] { dep.Origin, dep.Target })) + .Concat(packageIds) ); // In the order the slices appear, so the same diagram always gets the same ids. var aliases = new Dictionary(); - foreach (var name in sliceNames.Where(name => CharactersNotAllowedInId.IsMatch(name))) + foreach ( + var name in sliceNames.Where(name => + CharactersNotAllowedInId.IsMatch(name) || packageIds.Contains(name) + ) + ) { var alias = CharactersNotAllowedInId.Replace(name, "_"); var candidate = alias; diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs index 7432a2354..2384d3d3d 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs @@ -15,6 +15,12 @@ public class PlantUmlFileBuilder private List _sliceList = new List(); + /// + /// The deepest namespace that contains all types of each slice; see + /// . + /// + private Dictionary _commonNamespaces = new Dictionary(); + public string AsString(RenderOptions renderOptions = null) { if (renderOptions == null) @@ -122,6 +128,16 @@ public PlantUmlFileBuilder WithDependenciesFrom( return this; } + /// + /// Draws the given slices as components, with an arrow from one slice to another wherever + /// one of its types depends on one of the other's. + /// + /// The slices to draw, e.g. from GivenSlices.GetObjects. + /// Filters and layout options; the defaults if null. + /// With , every slice is drawn in the + /// package of the deepest namespace that contains all of its types. With + /// , only the arrows between slices whose + /// types share an equally deep namespace are drawn. public PlantUmlFileBuilder WithDependenciesFrom( IEnumerable slices, GenerationOptions generationOptions = null @@ -129,8 +145,8 @@ public PlantUmlFileBuilder WithDependenciesFrom( { _sliceList = slices.Distinct().ToList(); RemovePatternInappropriateSlices(); + AssignCommonNamespaces(); - var nodes = new Dictionary(); if (generationOptions == null) { generationOptions = new GenerationOptions(); @@ -138,7 +154,6 @@ public PlantUmlFileBuilder WithDependenciesFrom( foreach (var slice in _sliceList) { - var sliceIsPackage = IsPackage(slice); var dependencyTargets = SelectDependencies( slice, _sliceList, @@ -147,94 +162,52 @@ public PlantUmlFileBuilder WithDependenciesFrom( generationOptions ); - if (!sliceIsPackage) - { - var slc = new PlantUmlSlice(slice.Description, slice.NameSpace); - if (generationOptions.C4Style) - { - slc.UseS4Style(); - } - nodes.Add(slice, slc); - } - if (!generationOptions.LimitDependencies) { - if (sliceIsPackage) - { - _dependencies.AddRange( - dependencyTargets.Select(target => new PlantUmlDependency( - slice.Description, - target.Description, - DependencyType.PackageToOne - )) - ); - } - else - { - _dependencies.AddRange( - dependencyTargets.Select(target => new PlantUmlDependency( - slice.Description, - target.Description, - DependencyType.OneToOne - )) - ); - } + _dependencies.AddRange( + dependencyTargets.Select(target => new PlantUmlDependency( + slice.Description, + target.Description, + DependencyType.OneToOne + )) + ); } - else if (slice.ContainsNamespace()) + else { - if (sliceIsPackage) - { - _dependencies.AddRange( - dependencyTargets.Select(target => new PlantUmlDependency( - slice.Description, - target.Description, - DependencyType.PackageToPackageIfSameParentNamespace - )) - ); - } - else - { - var dt = dependencyTargets + _dependencies.AddRange( + dependencyTargets + .Where(target => + CountOfSegments(_commonNamespaces[target]) + == CountOfSegments(_commonNamespaces[slice]) + ) .Select(target => new PlantUmlDependency( slice.Description, target.Description, - DependencyType.OneToOneIfSameParentNamespace + DependencyType.OneToOneCompact )) - .Where(dependency => - dependency.OriginCountOfDots() < dependency.TargetCountOfDots() - || _sliceList.All(slc => - slc.Description == dependency.Target - || !slc.Description.Contains(dependency.Target) - ) - ); - _dependencies.AddRange(dt); - } - } - else - { - _dependencies.AddRange( - dependencyTargets.Select(target => new PlantUmlDependency( - slice.Description, - target.Description, - DependencyType.OneToOneCompact - )) ); } } - if (!generationOptions.IncludeNodesWithoutDependencies) - { - RemoveNodesNotContainedInDependencies(nodes, slice => slice.Description); - } - if (!generationOptions.LimitDependencies) { - RemoveDuplicateDependenciesWhenShowingPackages(); ReplaceCirclesWithAppropriateDependencyType(); } RemoveDuplicatedArrowsIfExist(); - _diagram.AddElements(nodes.Values); + var drawnSlices = generationOptions.IncludeNodesWithoutDependencies + ? _sliceList + : SlicesContainedInDependencies(); + var packages = AssignPackages(drawnSlices, generationOptions.NestSlicesInPackages); + foreach (var slice in drawnSlices) + { + var node = CreateNode(slice, packages); + if (generationOptions.C4Style) + { + node.UseS4Style(); + } + _diagram.AddElement(node); + } _diagram.AddElements(_dependencies); return this; @@ -275,6 +248,10 @@ public PlantUmlFileBuilder WithDependenciesFrom( target => target.Types, generationOptions ) + .Where(target => + !generationOptions.LimitDependencies + || CountOfSegments(target.FullName) == CountOfSegments(ns.FullName) + ) .Select(target => new PlantUmlDependency( ns.FullName, target.FullName, @@ -328,9 +305,21 @@ var parent in parentNamespaces.Where(parentName => return this; } + /// + /// Draws only the slices whose types all lie in the namespace + /// or below it, colored, and the slices they depend on or that depend on them, with the + /// arrows between them that start or end at one of the former. + /// + /// The slices to draw, e.g. from GivenSlices.GetObjects. + /// The namespace to focus on. + /// Only + /// is read; the defaults if null. + /// The package is empty, a single dot or a namespace + /// that no slice lies in. public PlantUmlFileBuilder WithDependenciesFromFocusOn( IEnumerable slices, - string package + string package, + GenerationOptions generationOptions = null ) { _sliceList = slices.Distinct().ToList(); @@ -348,114 +337,62 @@ string package package = package.Remove(package.Length - 1); } - RemovePatternInappropriateSlices(package); + RemovePatternInappropriateSlices(); + AssignCommonNamespaces(); - var existPackage = _sliceList.Any(slice => - (slice.NameSpace + slice.Description).Contains(package) + var focusedSlices = new HashSet( + _sliceList + .Where(slice => LiesIn(_commonNamespaces[slice], package)) + .Select(slice => slice.Description) ); - if (!existPackage) + if (focusedSlices.Count == 0) { - throw new ArgumentException( - "The package [" + package + "] is not contained in this slice" - ); + throw new ArgumentException("No slice lies in the namespace [" + package + "]"); } - var nodes = new Dictionary(); - var generationOptions = new GenerationOptions(); + var defaultOptions = new GenerationOptions(); foreach (var slice in _sliceList) { - var sliceIsPackage = IsPackage(slice); var dependencyTargets = SelectDependencies( slice, _sliceList, target => target.Description, target => target.Types, - generationOptions + defaultOptions ); - if (sliceIsPackage) - { - _dependencies.AddRange( - dependencyTargets.Select(target => new PlantUmlDependency( - slice.Description, - target.Description, - DependencyType.PackageToOne - )) - ); - } - else - { - _dependencies.AddRange( - dependencyTargets.Select(target => new PlantUmlDependency( - slice.Description, - target.Description, - DependencyType.OneToOne - )) - ); - } - - if (!sliceIsPackage) - { - nodes.Add( - slice, - slice.Description.Contains(package) - ? new PlantUmlSlice(slice.Description, slice.NameSpace, "99ffd1") - : new PlantUmlSlice(slice.Description, slice.NameSpace) - ); - } - else if (!slice.Description.Contains(package)) - { - nodes.Add(slice, new PlantUmlSlice(slice.Description + ".", slice.NameSpace)); - } - } - - if (!generationOptions.IncludeNodesWithoutDependencies) - { - RemoveNodesNotContainedInDependencies(nodes, slice => slice.Description); + _dependencies.AddRange( + dependencyTargets.Select(target => new PlantUmlDependency( + slice.Description, + target.Description, + DependencyType.OneToOne + )) + ); } - RemoveDuplicateDependenciesWhenShowingPackages(); ReplaceCirclesWithAppropriateDependencyType(); RemoveDuplicatedArrowsIfExist(); - for (var j = _dependencies.Count - 1; j >= 0; j--) - { - if ( - !_dependencies[j].Origin.Contains(package) - && !_dependencies[j].Target.Contains(package) - ) - { - _dependencies.RemoveAt(j); - } - } - - for (var i = nodes.Count - 1; i >= 0; i--) - { - var nodeExist = _dependencies.Any(dep => - nodes.ElementAt(i).Key.Description == dep.Origin - || nodes.ElementAt(i).Key.Description == dep.Target - ); - if (!nodeExist) - { - nodes.Remove(nodes.ElementAt(i).Key); - } - } - - _diagram.AddElements(nodes.Values); + _dependencies.RemoveAll(dependency => + !focusedSlices.Contains(dependency.Origin) + && !focusedSlices.Contains(dependency.Target) + ); - var tempList = _sliceList - .Where(slice => IsPackage(slice) && slice.Description.Contains(package)) - .Select(slice => new PlantUmlSlice( - slice.Description + ".", - slice.NameSpace, - "99ffd1" - )) - .ToList(); - if (tempList.Count > 0) - { - _diagram.AddElements(tempList.OrderByDescending(element => element.ToString())); - } + var drawnSlices = SlicesContainedInDependencies(); + var packages = AssignPackages( + drawnSlices, + generationOptions?.NestSlicesInPackages ?? false + ); + _diagram.AddElements( + drawnSlices.Select(slice => + CreateNode( + slice, + packages, + focusedSlices.Contains(slice.Description) ? "99ffd1" : null + ) + ) + ); _diagram.AddElements(_dependencies); return this; @@ -508,36 +445,20 @@ GenerationOptions generationOptions ); } - private void RemovePatternInappropriateSlices(string thatContainsThisString = null) + private void RemovePatternInappropriateSlices() { if (_sliceList.Any(slice => slice.CountOfAsteriskInPattern == null)) { return; } - if ( - thatContainsThisString != null - && !_sliceList.Any(slice => - (slice.NameSpace + slice.Description).Contains(thatContainsThisString) - ) - ) - { - return; - } for (var i = _sliceList.Count - 1; i >= 0; i--) { var dots = 0; - if ( - thatContainsThisString != null - && !(_sliceList[i].Description).Contains(thatContainsThisString) - ) - { - continue; - } - + // The description is never qualified, so its dots are those below the namespace + // the pattern matched before its capture group. dots += _sliceList[i].Description.Count(c => c == '.'); - dots -= (_sliceList[i].NameSpace ?? string.Empty).Count(c => c == '.'); if (dots >= _sliceList[i].CountOfAsteriskInPattern) { @@ -563,148 +484,197 @@ private void RemoveDuplicatedArrowsIfExist() } } - private void RemoveDuplicateDependenciesWhenShowingPackages() + private void ReplaceCirclesWithAppropriateDependencyType() { - if (_sliceList.Any(slice => !slice.ContainsNamespace())) - { - return; - } - - for (var j = _dependencies.Count - 1; j >= 0; j--) + for (var i = _dependencies.Count - 1; i >= 0; i--) { - if ( - _sliceList.Any(slice => - slice.Description.Contains(_dependencies[j].Target) - && slice.Description != _dependencies[j].Target - ) - ) + for (var j = i - 1; j >= 0; j--) { - if (_dependencies[j].Origin.Contains(_dependencies[j].Target)) - { - _dependencies.RemoveAt(j); - } - else if (_dependencies[j].DependencyType == DependencyType.PackageToOne) - { - _dependencies[j] = new PlantUmlDependency( - _dependencies[j].Origin, - _dependencies[j].Target, - DependencyType.PackageToPackage - ); - } - else + if ( + _dependencies[i].Target != _dependencies[j].Origin + || _dependencies[i].Origin != _dependencies[j].Target + ) { - _dependencies[j] = new PlantUmlDependency( - _dependencies[j].Origin, - _dependencies[j].Target, - DependencyType.OneToPackage - ); + continue; } - continue; - } - if (_dependencies[j].DependencyType != DependencyType.PackageToOne) - { - continue; - } - - if (_dependencies[j].Target.Contains(_dependencies[j].Origin)) - { - _dependencies.RemoveAt(j); + _dependencies.RemoveAt(i); + _dependencies[j] = new PlantUmlDependency( + _dependencies[j].Origin, + _dependencies[j].Target, + DependencyType.Circle + ); + break; } } + } - for (var i = _dependencies.Count - 1; i >= 0; i--) + /// + /// The slices that a dependency starts or ends at, in the order of the slice list. + /// + private List SlicesContainedInDependencies() + { + var namesInDependencies = new HashSet( + _dependencies.SelectMany(dep => new[] { dep.Origin, dep.Target }) + ); + return _sliceList + .Where(slice => namesInDependencies.Contains(slice.Description)) + .ToList(); + } + + private void AssignCommonNamespaces() + { + _commonNamespaces = _sliceList.ToDictionary(slice => slice, CommonNamespaceOf); + } + + /// + /// The deepest namespace that contains all types of the slice, i.e. the longest sequence + /// of leading segments their namespaces share, or an empty string if they share none. + /// + private static string CommonNamespaceOf(Slice slice) + { + string[] commonSegments = null; + var length = 0; + foreach (var type in slice.Types) { - if ( - _dependencies[i].DependencyType == DependencyType.PackageToOne - || _dependencies[i].DependencyType == DependencyType.PackageToPackage - ) + var segments = (type.Namespace?.FullName ?? "").Split('.'); + if (commonSegments == null) { - if ( - _dependencies.Any(dependency => - _dependencies[i].Target == dependency.Target - && dependency.Origin.Contains(_dependencies[i].Origin) - && dependency.Origin != _dependencies[i].Origin - ) - ) - { - _dependencies.RemoveAt(i); - continue; - } + commonSegments = segments; + length = segments.Length; + continue; } - if ( - _dependencies[i].DependencyType == DependencyType.OneToPackage - || _dependencies[i].DependencyType == DependencyType.PackageToPackage - ) + length = Math.Min(length, segments.Length); + for (var i = 0; i < length; i++) { - if ( - _dependencies.Any(dependency => - dependency.Target.Contains(_dependencies[i].Target) - && dependency.Target != _dependencies[i].Target - && dependency.Origin.Contains(_dependencies[i].Origin) - ) - ) + if (segments[i] != commonSegments[i]) { - _dependencies.RemoveAt(i); - continue; + length = i; + break; } } + } - if ( - _dependencies[i].Target.Contains(_dependencies[i].Origin) - && _dependencies[i].Target != _dependencies[i].Origin - ) + return commonSegments == null ? "" : string.Join(".", commonSegments.Take(length)); + } + + private static int CountOfSegments(string nameSpace) + { + return nameSpace == "" ? 0 : nameSpace.Count(c => c == '.') + 1; + } + + /// + /// Whether is or lies below it. + /// + private static bool LiesIn(string nameSpace, string package) + { + return nameSpace == package + || nameSpace.StartsWith(package + ".", StringComparison.Ordinal); + } + + /// + /// Decides for every slice the packages it is drawn in, outermost first, if slices are + /// nested at all. + /// + /// + /// The packages are the namespace tree of the slices' common namespaces, and each slice + /// sits in the package of its own common namespace. A package that holds no slice and + /// exactly one package is merged with that package into one labelled with both, e.g. + /// "Orders.Domain", down to the first package that holds a slice or several packages. So + /// the outermost package is a whole namespace such as "App.Orders". A slice whose types + /// share no namespace is in no package and is drawn on its own. + /// + /// The slices that are drawn. + /// + /// Whether the options ask for nesting; slices of MatchingWithPackages are nested + /// regardless. + /// + private Dictionary> AssignPackages( + List slices, + bool nestSlicesInPackages + ) + { + var packages = new Dictionary>(); + if (!nestSlicesInPackages && !slices.Any(slice => slice.NestByDefault)) + { + return packages; + } + + var nestedSlices = slices.Where(slice => _commonNamespaces[slice] != "").ToList(); + var root = new NamespaceNode(); + foreach (var slice in nestedSlices) + { + var node = root; + foreach (var segment in _commonNamespaces[slice].Split('.')) { - _dependencies.RemoveAt(i); + node = node.GetOrAddChild(segment); } + node.HoldsSlice = true; } - } - private void ReplaceCirclesWithAppropriateDependencyType() - { - for (var i = _dependencies.Count - 1; i >= 0; i--) + foreach (var slice in nestedSlices) { - for (var j = i - 1; j >= 0; j--) + var path = new List(); + var node = root; + foreach (var segment in _commonNamespaces[slice].Split('.')) { - if ( - _dependencies[i].Target != _dependencies[j].Origin - || _dependencies[i].Origin != _dependencies[j].Target - ) + var mergesWithChild = + node != root && !node.HoldsSlice && node.Children.Count == 1; + node = node.Children[segment]; + if (mergesWithChild) { - continue; + path[path.Count - 1] += "." + segment; + } + else + { + path.Add(segment); } - - _dependencies.RemoveAt(i); - _dependencies[j] = new PlantUmlDependency( - _dependencies[j].Origin, - _dependencies[j].Target, - DependencyType.Circle - ); - break; } + packages.Add(slice, path); } + + return packages; } - private bool IsPackage(Slice slice) + private static PlantUmlSlice CreateNode( + Slice slice, + Dictionary> packages, + string color = null + ) { - if (!slice.ContainsNamespace()) + if (!packages.TryGetValue(slice, out var path)) { - return false; + return new PlantUmlSlice(slice.Description, null, color); } - for (var i = _sliceList.Count - 1; i >= 0; i--) + return new PlantUmlSlice( + slice.Description, + path[0] + ".", + path.Skip(1).ToList(), + slice.Description, + color + ); + } + + /// A namespace in the tree that builds. + private class NamespaceNode + { + public readonly Dictionary Children = + new Dictionary(); + + public bool HoldsSlice; + + public NamespaceNode GetOrAddChild(string segment) { - if ( - _sliceList[i].Description != slice.Description - & _sliceList[i].Description.StartsWith(slice.Description) - ) + if (!Children.TryGetValue(segment, out var child)) { - return true; + child = new NamespaceNode(); + Children.Add(segment, child); } - } - return false; + return child; + } } } } diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs index 75ab17283..7c02c72b4 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs @@ -13,6 +13,10 @@ public class PlantUmlSlice : IPlantUmlElement private string Color { get; } private bool C4Style { get; set; } + // Set when the caller already knows where the slice sits; see the internal constructor. + private readonly IReadOnlyList _packages; + private readonly string _leaf; + public PlantUmlSlice( string name, string nameSpace = null, @@ -29,6 +33,31 @@ public PlantUmlSlice( Alias = name; } + /// + /// Creates a nested slice whose packages and label are given rather than read off its + /// name. + /// + /// The full slice name, which is also the alias arrows point at. + /// The root package, with a trailing ".". + /// The packages between the root package and the slice. + /// + /// The label inside the innermost package, or an empty string if the slice only stands + /// for that package. + /// + /// The background color, if any. + internal PlantUmlSlice( + string name, + string nameSpace, + IReadOnlyList packages, + string leaf, + string color = null + ) + : this(name, nameSpace, color) + { + _packages = packages; + _leaf = leaf; + } + public override string ToString() { return _name; @@ -66,12 +95,35 @@ internal IReadOnlyList PackagePath get { var path = new List { RootPackage }; + if (_packages != null) + { + path.AddRange(_packages); + return path; + } + var segments = _name.Remove(0, Namespace.Length).Split('.'); path.AddRange(segments.Take(segments.Length - 1)); return path; } } + /// + /// The ids of the packages this slice is nested in, outermost first; see + /// . + /// + internal IEnumerable PackageIds + { + get + { + string id = null; + foreach (var package in PackagePath) + { + id = id == null ? package : id + "." + package; + yield return id; + } + } + } + /// /// The label of the component inside its innermost package, or an empty string if the /// slice only stands for that package. @@ -80,6 +132,11 @@ internal string Leaf { get { + if (_leaf != null) + { + return _leaf; + } + var name = _name.Remove(0, Namespace.Length); return name.Substring(name.LastIndexOf('.') + 1); } diff --git a/ArchUnitNET/Domain/Slice.cs b/ArchUnitNET/Domain/Slice.cs index 658a06596..0c1bb482b 100644 --- a/ArchUnitNET/Domain/Slice.cs +++ b/ArchUnitNET/Domain/Slice.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using ArchUnitNET.Domain.Dependencies; -using JetBrains.Annotations; namespace ArchUnitNET.Domain { @@ -27,10 +26,15 @@ public Slice(SliceIdentifier identifier, IEnumerable types) public string Description => Identifier.Description; - [CanBeNull] - public string NameSpace => Identifier.NameSpace; public int? CountOfAsteriskInPattern => Identifier.CountOfAsteriskInPattern; + /// + /// Whether diagrams nest this slice in packages even if + /// is not set, which + /// is the case for slices of the obsolete MatchingWithPackages. + /// + internal bool NestByDefault { get; set; } + protected bool Equals(Slice other) { return Equals(Identifier, other.Identifier); @@ -65,10 +69,5 @@ public override string ToString() { return Description; } - - public bool ContainsNamespace() - { - return NameSpace != null; - } } } diff --git a/ArchUnitNET/Domain/SliceIdentifier.cs b/ArchUnitNET/Domain/SliceIdentifier.cs index e84a4a539..60af37821 100644 --- a/ArchUnitNET/Domain/SliceIdentifier.cs +++ b/ArchUnitNET/Domain/SliceIdentifier.cs @@ -1,6 +1,4 @@ -using JetBrains.Annotations; - -namespace ArchUnitNET.Domain +namespace ArchUnitNET.Domain { public class SliceIdentifier : StringIdentifier, IHasDescription { @@ -10,29 +8,21 @@ public class SliceIdentifier : StringIdentifier, IHasDescription private SliceIdentifier( string identifier, bool ignored, - int? countOfAsteriskInPattern = null, - string nameSpace = null + int? countOfAsteriskInPattern = null ) : base(identifier) { Ignored = ignored; CountOfAsteriskInPattern = countOfAsteriskInPattern; - NameSpace = nameSpace; } public string Description => Identifier; - [CanBeNull] - public readonly string NameSpace; public readonly int? CountOfAsteriskInPattern; - public static SliceIdentifier Of( - string identifier, - int? countOfAsteriskInPattern = null, - string nameSpace = null - ) + public static SliceIdentifier Of(string identifier, int? countOfAsteriskInPattern = null) { - return new SliceIdentifier(identifier, false, countOfAsteriskInPattern, nameSpace); + return new SliceIdentifier(identifier, false, countOfAsteriskInPattern); } public static SliceIdentifier Ignore() diff --git a/ArchUnitNET/Fluent/PlantUml/PlantUmlFluentComponentDiagramInitializer.cs b/ArchUnitNET/Fluent/PlantUml/PlantUmlFluentComponentDiagramInitializer.cs index 2bc3f92bc..2be5fef63 100644 --- a/ArchUnitNET/Fluent/PlantUml/PlantUmlFluentComponentDiagramInitializer.cs +++ b/ArchUnitNET/Fluent/PlantUml/PlantUmlFluentComponentDiagramInitializer.cs @@ -65,14 +65,25 @@ public GivenPlantUmlFluentComponentDiagram WithDependenciesFromSlices( return new GivenPlantUmlFluentComponentDiagram(_fluentComponentDiagramCreator); } + /// + /// Draws only the slices whose types all lie in the namespace + /// or below it, and the slices they depend on or that + /// depend on them; see . + /// + /// The slices to draw. + /// The namespace to focus on. + /// Only + /// is read. public GivenPlantUmlFluentComponentDiagram WithDependenciesFromSlices( IEnumerable slices, - string focusOnPackage + string focusOnPackage, + GenerationOptions generationOptions = null ) { _fluentComponentDiagramCreator.Builder.WithDependenciesFromFocusOn( slices, - focusOnPackage + focusOnPackage, + generationOptions ); _fluentComponentDiagramCreator.AddToDescription("with dependencies from slices"); return new GivenPlantUmlFluentComponentDiagram(_fluentComponentDiagramCreator); diff --git a/ArchUnitNET/Fluent/Slices/SliceAssignment.cs b/ArchUnitNET/Fluent/Slices/SliceAssignment.cs index db5bdf1c4..23a89a9ff 100644 --- a/ArchUnitNET/Fluent/Slices/SliceAssignment.cs +++ b/ArchUnitNET/Fluent/Slices/SliceAssignment.cs @@ -12,19 +12,35 @@ public class SliceAssignment : IHasDescription public SliceAssignment( Func assignIdentifierFunc, string description + ) + : this(assignIdentifierFunc, description, false) { } + + internal SliceAssignment( + Func assignIdentifierFunc, + string description, + bool nestByDefault ) { _assignIdentifierFunc = assignIdentifierFunc; Description = description; + _nestByDefault = nestByDefault; } + /// + /// Whether diagrams nest the slices in packages even if + /// is not set; + /// only for the obsolete MatchingWithPackages. + /// + private readonly bool _nestByDefault; + public string Description { get; } public IEnumerable Apply(IEnumerable types) { return types.GroupBy( _assignIdentifierFunc, - (identifier, enumerable) => new Slice(identifier, enumerable), + (identifier, enumerable) => + new Slice(identifier, enumerable) { NestByDefault = _nestByDefault }, SliceIdentifier.Comparer ); } diff --git a/ArchUnitNET/Fluent/Slices/SliceRuleInitializer.cs b/ArchUnitNET/Fluent/Slices/SliceRuleInitializer.cs index 228de6820..00b67c11f 100644 --- a/ArchUnitNET/Fluent/Slices/SliceRuleInitializer.cs +++ b/ArchUnitNET/Fluent/Slices/SliceRuleInitializer.cs @@ -21,29 +21,36 @@ public SliceRuleInitializer(SliceRuleCreator ruleCreator) /// public GivenSlices Matching(string pattern) { - _ruleCreator.SetSliceAssignment( - new SliceAssignment( - t => - { - var parseResult = Parse(pattern); - return AssignFunc(t, parseResult.pattern, parseResult.asterisk); - }, - "matching \"" + pattern + "\"" - ) - ); - return new GivenSlices(_ruleCreator); + return Matching(pattern, false); } + /// + /// Creates the same slices as , which PlantUML diagrams nest in + /// package blocks even without + /// . + /// + /// The namespace pattern, as for . + [Obsolete( + "Use Matching(pattern) and draw the slices with " + + "new GenerationOptions { NestSlicesInPackages = true }. MatchingWithPackages " + + "creates the same slices as Matching and will be removed in a future release." + )] public GivenSlices MatchingWithPackages(string pattern) + { + return Matching(pattern, true); + } + + private GivenSlices Matching(string pattern, bool nestByDefault) { _ruleCreator.SetSliceAssignment( new SliceAssignment( t => { var parseResult = Parse(pattern); - return AssignFunc(t, parseResult.pattern, parseResult.asterisk, true); + return AssignFunc(t, parseResult.pattern, parseResult.asterisk); }, - "matching \"" + pattern + "\"" + "matching \"" + pattern + "\"", + nestByDefault ) ); return new GivenSlices(_ruleCreator); @@ -87,8 +94,7 @@ private static (string pattern, int? asterisk) Parse(string pattern) private static SliceIdentifier AssignFunc( IType type, string pattern, - int? countOfSingleAsterisk, - bool fullName = false + int? countOfSingleAsterisk ) { var indexOfAsteriskInPattern = pattern.IndexOf("(*", StringComparison.Ordinal); @@ -161,15 +167,7 @@ private static SliceIdentifier AssignFunc( ); } - // An empty prefix means the pattern starts with its capture group; there is no - // namespace to nest the slice under, so report none rather than an empty one. - return fullName - ? SliceIdentifier.Of( - slicePrefix + sliceString, - countOfSingleAsterisk, - slicePrefix == "" ? null : slicePrefix - ) - : SliceIdentifier.Of(sliceString, countOfSingleAsterisk); + return SliceIdentifier.Of(sliceString, countOfSingleAsterisk); } } } diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs index 64302e59f..95cbfbf97 100644 --- a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs +++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs @@ -65,6 +65,22 @@ public Task BuildUmlByNamespacesSnapshotTest() return Verifier.Verify(uml).DisableDiff().UseDirectory("Snapshots"); } + [Fact] + public Task BuildUmlByNamespacesLimitDependenciesSnapshotTest() + { + // Only the arrows between namespaces with the same number of segments are drawn. + var namespaces = StaticTestArchitectures.SlicesTestArchitecture.Namespaces.Where(ns => + ns.FullName.StartsWith("SlicesTestAssembly.MultipleSubnamespaces") + ); + var uml = new PlantUmlFileBuilder() + .WithDependenciesFrom( + namespaces, + new GenerationOptions { LimitDependencies = true } + ) + .AsString(); + return Verifier.Verify(uml).DisableDiff().UseDirectory("Snapshots"); + } + [Fact] public void BuildUmlBySlicesTest() { @@ -187,6 +203,20 @@ public Task SliceNamesThatAreNotValidIdsTest() ); } + [Fact] + public Task SliceNamesThatArePackageIdsTest() + { + // Each slice is named like the package it is drawn in, so both get an id of their own. + return VerifyElements( + new IPlantUmlElement[] + { + new PlantUmlSlice("A.X", "A.", new[] { "X" }, "A.X"), + new PlantUmlSlice("A.X.Y", "A.", new[] { "X", "Y" }, "A.X.Y"), + new PlantUmlDependency("A.X.Y", "A.X", DependencyType.OneToOne), + } + ); + } + [Fact] public Task C4StyleSliceNamesThatAreNotValidIdsTest() { @@ -246,6 +276,21 @@ public void PackageArrowsTest(DependencyType dependencyType, string expected) Assert.Equal(expected + Environment.NewLine, dependency.GetPlantUmlString()); } + [Fact] + public void OneToOneCompactArrowIgnoresDotsTest() + { + // The exporter decides which compact arrows to draw, not the arrow itself. + var dependency = new PlantUmlDependency( + "A.Web", + "A.Billing.Domain", + DependencyType.OneToOneCompact + ); + Assert.Equal( + "[A.Web] --> [A.Billing.Domain]" + Environment.NewLine, + dependency.GetPlantUmlString() + ); + } + [Fact] public void PackageToPackageIfSameParentNamespaceArrowTest() { diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs index 824bece2b..974e9a766 100644 --- a/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs +++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using ArchUnitNET.Domain; using ArchUnitNET.Domain.PlantUml.Export; @@ -22,12 +23,15 @@ public class PlantUmlSliceDiagramTests private const string Root = "SlicesTestAssembly.MultipleSubnamespaces."; + private static GenerationOptions Nested => + new GenerationOptions { NestSlicesInPackages = true }; + // --- The asterisk count decides which slices the exporter renders --------------- // // Every pattern below matches the same nine slices -- SlicesTests.MatchingTest and // Matching_SingleAsterisk_CapturesEveryDepth assert that. What differs is how many // of them survive PlantUmlFileBuilder.RemovePatternInappropriateSlices, which drops - // a slice once its depth (dots in Description, less dots in NameSpace) reaches + // a slice once its depth (dots in its Description) reaches // Slice.CountOfAsteriskInPattern: // // (*) count 1 -> 3 of 9 slices rendered @@ -54,7 +58,7 @@ public Task BuildUmlBySlices_DoubleAsterisk() // The matcher discards everything after the first "(*", so "(*).(*)" and "(*)..(*)" // are indistinguishable to it and the next two snapshots are byte-identical -- as are - // their MatchingWithPackages counterparts below. That identity is the point: it is + // their nested counterparts below. That identity is the point: it is // what a reimplementation giving the second capture group any meaning would have to // change. SlicesTests.DotDot_* cover the shapes that would tell the two patterns // apart. @@ -76,64 +80,60 @@ public Task BuildUmlBySlices_NonContiguousCaptureGroups() } [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_SingleAsterisk() + public Task BuildUmlBySlicesNested_SingleAsterisk() { - return VerifySlices(SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(*)")); + return VerifySlices(SliceRuleDefinition.Slices().Matching(Root + "(*)"), Nested); } [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk() + public Task BuildUmlBySlicesNested_DoubleAsterisk() { - return VerifySlices(SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(**)")); + return VerifySlices(SliceRuleDefinition.Slices().Matching(Root + "(**)"), Nested); } [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups() + public Task BuildUmlBySlicesNested_MultipleCaptureGroups() { - return VerifySlices( - SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(*).(*)") - ); + return VerifySlices(SliceRuleDefinition.Slices().Matching(Root + "(*).(*)"), Nested); } [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups() + public Task BuildUmlBySlicesNested_NonContiguousCaptureGroups() { - return VerifySlices( - SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(*)..(*)") - ); + return VerifySlices(SliceRuleDefinition.Slices().Matching(Root + "(*)..(*)"), Nested); } [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_CaptureGroupAtStart() + public Task BuildUmlBySlicesNested_CaptureGroupAtStart() { - // Nothing precedes the capture group, so there is no namespace to nest the slices - // under and they are drawn flat, like those of Matching. - return VerifySlices(SliceRuleDefinition.Slices().MatchingWithPackages("(**).Service")); + // Nothing precedes the capture group, so every slice is named after a whole + // namespace, but it is nested where its types are: in that namespace's "Service". + return VerifySlices(SliceRuleDefinition.Slices().Matching("(**).Service"), Nested); } [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_LimitDependencies() + public Task BuildUmlBySlicesNested_LimitDependencies() { return VerifySlices( - SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(**)"), - new GenerationOptions { LimitDependencies = true } + SliceRuleDefinition.Slices().Matching(Root + "(**)"), + new GenerationOptions { NestSlicesInPackages = true, LimitDependencies = true } ); } [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_C4Style() + public Task BuildUmlBySlicesNested_C4Style() { return VerifySlices( - SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(**)"), - new GenerationOptions { C4Style = true } + SliceRuleDefinition.Slices().Matching(Root + "(**)"), + new GenerationOptions { NestSlicesInPackages = true, C4Style = true } ); } [Fact] public Task BuildUmlBySlices_C4Style() { - // Slices produced by Matching (as opposed to MatchingWithPackages) have no namespace - // prefix, so this exercises PlantUmlSlice.BuildStringC4Style's no-namespace branch. + // Slices that are not nested have no namespace prefix, so this exercises + // PlantUmlSlice.BuildStringC4Style's no-namespace branch. return VerifySlices( SliceRuleDefinition.Slices().Matching(Root + "(**)"), new GenerationOptions { C4Style = true } @@ -143,11 +143,9 @@ public Task BuildUmlBySlices_C4Style() [Fact] public Task BuildUmlBySlicesFocusOn() { - var slices = SortedSlices( - SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(**)") - ); + var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)")); var uml = new PlantUmlFileBuilder() - .WithDependenciesFromFocusOn(slices, Root + "Slice1") + .WithDependenciesFromFocusOn(slices, Root + "Slice1", Nested) .AsString(); return VerifyUml(uml); } @@ -155,13 +153,12 @@ public Task BuildUmlBySlicesFocusOn() [Fact] public Task BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices() { - // Slices produced by Matching 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. - // Their Description carries no Root prefix either, hence the bare focus string. + // Slices that are not nested have no namespace, so the focused ones take + // PlantUmlSlice.BuildString's namespace-less branch with a colour set, which renders + // "[Slice1] #99ffd1". The focus string is a namespace, flat or nested. var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)")); var uml = new PlantUmlFileBuilder() - .WithDependenciesFromFocusOn(slices, "Slice1") + .WithDependenciesFromFocusOn(slices, Root + "Slice1") .AsString(); return VerifyUml(uml); } @@ -179,14 +176,12 @@ public Task BuildUmlBySlices_DirectCircle_RendersCircleDependency() } [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_DirectCircle() + public Task BuildUmlBySlicesNested_DirectCircle() { var slices = SortedSlices( - SliceRuleDefinition - .Slices() - .MatchingWithPackages("SlicesTestAssembly.DirectCircle.(*)") + SliceRuleDefinition.Slices().Matching("SlicesTestAssembly.DirectCircle.(*)") ); - var uml = new PlantUmlFileBuilder().WithDependenciesFrom(slices).AsString(); + var uml = new PlantUmlFileBuilder().WithDependenciesFrom(slices, Nested).AsString(); return VerifyUml(uml); } @@ -199,23 +194,27 @@ public Task BuildUmlBySlicesMatchingWithPackages_DirectCircle() private const string SameNamedRoot = "SlicesTestAssembly.SameNamedSubnamespaces."; [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_SameNamedPackages() + public Task BuildUmlBySlicesNested_SameNamedPackages() { return VerifySlices( - SliceRuleDefinition.Slices().MatchingWithPackages(SameNamedRoot + "(**)") + SliceRuleDefinition.Slices().Matching(SameNamedRoot + "(**)"), + Nested ); } [Fact] - public Task BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style() + public Task BuildUmlBySlicesNested_SameNamedPackages_C4Style() { return VerifySlices( - SliceRuleDefinition.Slices().MatchingWithPackages(SameNamedRoot + "(**)"), - new GenerationOptions { C4Style = true } + SliceRuleDefinition.Slices().Matching(SameNamedRoot + "(**)"), + new GenerationOptions { NestSlicesInPackages = true, C4Style = true } ); } - // --- LimitDependencies without namespaces takes the OneToOneCompact branch ---- + // --- LimitDependencies only keeps arrows between slices at the same depth ------ + // + // A slice's depth is that of the namespace all its types share, so Slice2.Service -> + // Slice3 and Slice3.Group1 -> Slice3 are dropped, flat and nested alike. [Fact] public Task BuildUmlBySlices_LimitDependencies_Compact() @@ -272,10 +271,10 @@ public void BuildUmlBySlicesFocusOn_EmptyPackage_Throws() { var slices = SliceRuleDefinition .Slices() - .MatchingWithPackages(Root + "(**)") + .Matching(Root + "(**)") .GetObjects(Architecture); var ex = Assert.Throws(() => - new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, "") + new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, "", Nested) ); Assert.Equal("Package can't be empty", ex.Message); } @@ -285,10 +284,10 @@ public void BuildUmlBySlicesFocusOn_DotOnlyPackage_Throws() { var slices = SliceRuleDefinition .Slices() - .MatchingWithPackages(Root + "(**)") + .Matching(Root + "(**)") .GetObjects(Architecture); var ex = Assert.Throws(() => - new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, ".") + new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, ".", Nested) ); Assert.Equal("Package can't contain a single dot only", ex.Message); } @@ -296,17 +295,32 @@ public void BuildUmlBySlicesFocusOn_DotOnlyPackage_Throws() [Fact] public void BuildUmlBySlicesFocusOn_UnknownPackage_Throws() { - // A single-asterisk pattern gives every slice a non-null CountOfAsteriskInPattern, - // which is required to reach RemovePatternInappropriateSlices' early return for a - // focus string that no slice contains, on the way to this exception. var slices = SliceRuleDefinition .Slices() - .MatchingWithPackages(Root + "(*)") + .Matching(Root + "(*)") .GetObjects(Architecture); var ex = Assert.Throws(() => - new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, "NotAPackage") + new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, "NotAPackage", Nested) + ); + Assert.Equal("No slice lies in the namespace [NotAPackage]", ex.Message); + } + + [Fact] + public void BuildUmlBySlicesFocusOn_NamespaceIsMatchedBySegments() + { + // The substring "Slice" is contained in every slice name, but no slice lies in a + // namespace called "Slice". + var slices = SliceRuleDefinition + .Slices() + .Matching(Root + "(**)") + .GetObjects(Architecture); + var ex = Assert.Throws(() => + new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, Root + "Slice") + ); + Assert.Equal( + "No slice lies in the namespace [SlicesTestAssembly.MultipleSubnamespaces.Slice]", + ex.Message ); - Assert.Contains("is not contained in this slice", ex.Message); } [Fact] @@ -314,39 +328,186 @@ public void BuildUmlBySlicesFocusOn_TrailingDotPackage_IsTrimmed() { var slicesWithTrailingDot = SliceRuleDefinition .Slices() - .MatchingWithPackages(Root + "(**)") + .Matching(Root + "(**)") .GetObjects(Architecture); var umlWithTrailingDot = new PlantUmlFileBuilder() - .WithDependenciesFromFocusOn(slicesWithTrailingDot, Root + "Slice1.") + .WithDependenciesFromFocusOn(slicesWithTrailingDot, Root + "Slice1.", Nested) .AsString(); var slicesWithoutTrailingDot = SliceRuleDefinition .Slices() - .MatchingWithPackages(Root + "(**)") + .Matching(Root + "(**)") .GetObjects(Architecture); var umlWithoutTrailingDot = new PlantUmlFileBuilder() - .WithDependenciesFromFocusOn(slicesWithoutTrailingDot, Root + "Slice1") + .WithDependenciesFromFocusOn(slicesWithoutTrailingDot, Root + "Slice1", Nested) .AsString(); Assert.Equal(umlWithoutTrailingDot, umlWithTrailingDot); } - // --- RemovePatternInappropriateSlices' per-slice "continue" for FocusOn ------ + // --- Which slices FocusOn focuses ------------------------------------------------ + // + // A slice is focused when all of its types lie in the namespace focused on or below + // it, which is a question about the types, so it gets the same answer flat and nested. [Fact] - public Task BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString() + public Task BuildUmlBySlicesFocusOn_SingleAsteriskPattern() { - // Focusing on the single-segment slice "Slice3" means slices whose Description - // doesn't contain it (e.g. "Slice1") skip the pattern-appropriateness check via - // RemovePatternInappropriateSlices' per-slice "continue", while "Slice3" itself is - // shallow enough to survive that check and keep the package reachable. + // RemovePatternInappropriateSlices leaves the three slices "(*)" stands for, of + // which "Slice3" lies in the namespace focused on. + var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(*)")); + var uml = new PlantUmlFileBuilder() + .WithDependenciesFromFocusOn(slices, Root + "Slice3", Nested) + .AsString(); + return VerifyUml(uml); + } + + [Fact] + public void BuildUmlBySlicesFocusOn_SiblingWithSamePrefix_IsNotFocused() + { + // OrdersArchive starts with "Orders", but does not lie in the namespace Orders. It + // is drawn as a neighbour, because it depends on Orders. var slices = SortedSlices( - SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(*)") + SliceRuleDefinition.Slices().Matching("SlicesTestAssembly.PrefixSiblings.(*)") ); var uml = new PlantUmlFileBuilder() + .WithDependenciesFromFocusOn(slices, "SlicesTestAssembly.PrefixSiblings.Orders") + .AsString(); + + Assert.Contains("[Orders] #99ffd1", uml); + Assert.Contains("[OrdersArchive]" + Environment.NewLine, uml); + } + + [Fact] + public void BuildUmlBySlicesFocusOn_SliceSpanningTheNamespace_IsANeighbour() + { + // "Services" has types in Slice1 and Slice2, so focusing on Slice1 does not focus it, + // and neither does focusing on Slice3, which it depends on. + var slices = SlicesWithServices(); + var uml = new PlantUmlFileBuilder() + .WithDependenciesFromFocusOn(slices, Root + "Slice3", Nested) + .AsString(); + + Assert.Contains("[Slice3] as Slice3 #99ffd1", uml); + Assert.Contains("[Services] as Services" + Environment.NewLine, uml); + Assert.Contains("[Services] --|> [Slice3]", uml); + } + + [Fact] + public void BuildUmlBySlicesFocusOn_FocusesTheSameSlicesFlatAndNested() + { + var slices = SlicesWithServices(); + + var flat = new PlantUmlFileBuilder() .WithDependenciesFromFocusOn(slices, Root + "Slice3") .AsString(); - return VerifyUml(uml); + var nested = new PlantUmlFileBuilder() + .WithDependenciesFromFocusOn(slices, Root + "Slice3", Nested) + .AsString(); + + Assert.Equal(DrawnSlices(flat), DrawnSlices(nested)); + Assert.Contains("Slice3.Group1 (focused)", DrawnSlices(flat)); + } + + // --- Where nested slices are drawn ----------------------------------------------- + // + // A nested slice sits in the package of the deepest namespace that contains all of its + // types. The packages come from those namespaces, not from the pattern. + + [Fact] + public Task BuildUmlBySlicesNested_DuplicatePrefixSegment() + { + // The leading ".." matches "SlicesTestAssembly.DuplicatePrefix.Sub.", but that is + // not what the package is made of: the slice's type lies in + // "SlicesTestAssembly.DuplicatePrefix.Sub.DuplicatePrefix.Sub", and there is no + // package ".DuplicatePrefix.Sub". + return VerifySlices( + SliceRuleDefinition.Slices().Matching("..DuplicatePrefix.Sub.(**)"), + Nested + ); + } + + [Fact] + public void BuildUmlBySlicesNested_SliceSitsInItsOwnNamespace() + { + var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)")); + var uml = new PlantUmlFileBuilder().WithDependenciesFrom(slices, Nested).AsString(); + + Assert.Contains( + Lines( + " package \"Slice3\" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 {", + " [Slice3] as Slice3", + " package \"Group1\" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 {", + " [Slice3.Group1] as Slice3.Group1" + ), + uml + ); + } + + [Fact] + public void BuildUmlBySlicesNested_PackageWithASinglePackage_IsMerged() + { + // Orders holds no slice of its own and only the package Domain. + var slices = SortedSlices( + SliceRuleDefinition.Slices().Matching(SameNamedRoot + "(**)") + ); + var uml = new PlantUmlFileBuilder().WithDependenciesFrom(slices, Nested).AsString(); + + Assert.Contains( + "package \"Orders.Domain\" as SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain {", + uml + ); + Assert.DoesNotContain("package \"Orders\"", uml); + } + + [Fact] + public void BuildUmlBySlicesNested_CustomSliceAssignment() + { + var slices = SlicesWithServices(); + var uml = new PlantUmlFileBuilder().WithDependenciesFrom(slices, Nested).AsString(); + + Assert.Contains( + Lines( + "package \"SlicesTestAssembly.MultipleSubnamespaces\" as SlicesTestAssembly.MultipleSubnamespaces {", + " [Services] as Services" + ), + uml + ); + } + + [Fact] + public void BuildUmlBySlicesNested_SliceWithoutCommonNamespace_IsDrawnOnItsOwn() + { + var slice1 = Architecture.Types.Single(type => + type.Name == "Slice1Class" && type.Namespace.FullName == Root + "Slice1" + ); + var objectType = Architecture.ReferencedTypes.Single(type => + type.FullName == "System.Object" + ); + var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)")) + .Concat( + new[] { new Slice(SliceIdentifier.Of("Mixed"), new[] { slice1, objectType }) } + ) + .ToList(); + var uml = new PlantUmlFileBuilder().WithDependenciesFrom(slices, Nested).AsString(); + + Assert.Contains(Environment.NewLine + "[Mixed]" + Environment.NewLine, uml); + } + + // --- One set of slices, drawn flat and nested ------------------------------------ + + [Fact] + public void BuildUmlBySlices_SameSlicesDrawnFlatAndNested() + { + var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)")); + + var flat = new PlantUmlFileBuilder().WithDependenciesFrom(slices).AsString(); + var nested = new PlantUmlFileBuilder().WithDependenciesFrom(slices, Nested).AsString(); + + Assert.DoesNotContain("package", flat); + Assert.Contains("[Slice1.Service]", flat); + Assert.Contains("package \"SlicesTestAssembly.MultipleSubnamespaces\"", nested); + Assert.Contains("[Slice1.Service] as Slice1.Service", nested); } /// @@ -363,6 +524,59 @@ private static Slice[] SortedSlices(GivenSlices slices) .ToArray(); } + /// + /// The slices of a custom assignment that gives every namespace below + /// its own slice, except Slice1.Service and Slice2.Service, which form the single slice + /// "Services". + /// + private static Slice[] SlicesWithServices() + { + var assignment = new SliceAssignment( + type => + { + var nameSpace = type.Namespace?.FullName ?? ""; + if (!nameSpace.StartsWith(Root, StringComparison.Ordinal)) + { + return SliceIdentifier.Ignore(); + } + + return nameSpace.EndsWith(".Service", StringComparison.Ordinal) + ? SliceIdentifier.Of("Services") + : SliceIdentifier.Of(nameSpace.Substring(Root.Length)); + }, + "custom" + ); + return assignment + .Apply(Architecture.Types) + .Where(slice => !slice.Identifier.Ignored) + .OrderBy(slice => slice.Description, StringComparer.Ordinal) + .ToArray(); + } + + /// + /// The slices a diagram declares, and whether each one is colored as focused. + /// + private static string[] DrawnSlices(string uml) + { + return uml.Split(new[] { Environment.NewLine }, StringSplitOptions.None) + .Select(line => SliceDeclaration.Match(line)) + .Where(match => match.Success) + .Select(match => + match.Groups["name"].Value + (match.Groups["color"].Success ? " (focused)" : "") + ) + .OrderBy(name => name, StringComparer.Ordinal) + .ToArray(); + } + + private static readonly Regex SliceDeclaration = new Regex( + @"^\s*\[(?[^\]]+)\](?: as \S+)?(? #99ffd1)?$" + ); + + private static string Lines(params string[] lines) + { + return string.Join(Environment.NewLine, lines); + } + private static Task VerifyUml(string uml) { return Verifier diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByNamespacesLimitDependenciesSnapshotTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByNamespacesLimitDependenciesSnapshotTest.verified.txt new file mode 100644 index 000000000..8d034160f --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByNamespacesLimitDependenciesSnapshotTest.verified.txt @@ -0,0 +1,33 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +namespace SlicesTestAssembly { +} +namespace SlicesTestAssembly.MultipleSubnamespaces { +} +namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3 { +} +namespace SlicesTestAssembly.MultipleSubnamespaces.Slice2 { +} +namespace SlicesTestAssembly.MultipleSubnamespaces.Slice1 { +} +namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 { +} +namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 { +} +namespace SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service { +} +namespace SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service { +} +namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner { +} +namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner { +} +[SlicesTestAssembly.MultipleSubnamespaces.Slice3] --> [SlicesTestAssembly.MultipleSubnamespaces.Slice1] +[SlicesTestAssembly.MultipleSubnamespaces.Slice2] --> [SlicesTestAssembly.MultipleSubnamespaces.Slice3] +[SlicesTestAssembly.MultipleSubnamespaces.Slice1] --> [SlicesTestAssembly.MultipleSubnamespaces.Slice2] +[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SliceNamesThatArePackageIdsTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SliceNamesThatArePackageIdsTest.verified.txt new file mode 100644 index 000000000..041c19d94 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SliceNamesThatArePackageIdsTest.verified.txt @@ -0,0 +1,17 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package "A" as A { + package "X" as A.X { + [A.X] as A.X_2 + package "Y" as A.X.Y { + [A.X.Y] as A.X.Y_2 + } + } +} + +[A.X.Y_2] --|> [A.X_2] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt index 28668d82b..86cc1d774 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt @@ -5,17 +5,24 @@ HIDE_STEREOTYPE() package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { - package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 #99ffd1 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service #99ffd1 + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { + [Slice1] as Slice1 #99ffd1 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service { + [Slice1.Service] as Slice1.Service #99ffd1 + } } package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service + [Slice2] as Slice2 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service { + [Slice2.Service] as Slice2.Service + } } package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { - label " " as SlicesTestAssembly.MultipleSubnamespaces.Slice3.__empty__ + [Slice3] as Slice3 } } -[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 +[Slice1] --|> [Slice2] +[Slice1.Service] --|> [Slice2.Service] +[Slice3] --|> [Slice1] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern.verified.txt new file mode 100644 index 000000000..4f22ba744 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern.verified.txt @@ -0,0 +1,21 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { + [Slice1] as Slice1 + } + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { + [Slice2] as Slice2 + } + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { + [Slice3] as Slice3 #99ffd1 + } +} + +[Slice2] --|> [Slice3] +[Slice3] --|> [Slice1] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt deleted file mode 100644 index 37d98cbf3..000000000 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt +++ /dev/null @@ -1,19 +0,0 @@ -@startuml - -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml - -HIDE_STEREOTYPE() - -package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { - package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { - label " " as SlicesTestAssembly.MultipleSubnamespaces.Slice1.__empty__ - } - package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service - } - [Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3 #99ffd1 -} - -[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice3] -[SlicesTestAssembly.MultipleSubnamespaces.Slice3] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 -@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt deleted file mode 100644 index b042a3dba..000000000 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt +++ /dev/null @@ -1,27 +0,0 @@ -@startuml - -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml - -HIDE_STEREOTYPE() - -Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) { - Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice1, Slice1) { - Container(SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service, Service) - } - Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice2, Slice2) { - Container(SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service, Service) - } - Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3, Slice3) { - Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1, Group1) { - Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner, Inner) - } - Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2, Group2) { - Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner, Inner) - } - } -} - -[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice3 -SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 -@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_CaptureGroupAtStart.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_CaptureGroupAtStart.verified.txt deleted file mode 100644 index 3e4dd4a96..000000000 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_CaptureGroupAtStart.verified.txt +++ /dev/null @@ -1,11 +0,0 @@ -@startuml - -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml - -HIDE_STEREOTYPE() - -[SlicesTestAssembly.DotDotSemantics.Alpha] -[SlicesTestAssembly.MultipleSubnamespaces.Slice1] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2] -[SlicesTestAssembly.MultipleSubnamespaces.Slice1] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2] -@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt deleted file mode 100644 index 882f21891..000000000 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt +++ /dev/null @@ -1,27 +0,0 @@ -@startuml - -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml - -HIDE_STEREOTYPE() - -package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { - package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service - } - package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service - } - package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { - package "Group1" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 { - [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner - } - package "Group2" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 { - [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner - } - } -} - -[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice3 -SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 -@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt deleted file mode 100644 index 58bf4a94f..000000000 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt +++ /dev/null @@ -1,23 +0,0 @@ -@startuml - -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml - -HIDE_STEREOTYPE() - -package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { - package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service - } - package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service - } - package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { - [Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 - [Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 - } -} - -[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice3 -SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 -@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt deleted file mode 100644 index 58bf4a94f..000000000 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt +++ /dev/null @@ -1,23 +0,0 @@ -@startuml - -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml - -HIDE_STEREOTYPE() - -package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { - package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service - } - package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service - } - package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { - [Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 - [Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 - } -} - -[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice3 -SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 -@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt deleted file mode 100644 index 5338f9f99..000000000 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt +++ /dev/null @@ -1,23 +0,0 @@ -@startuml - -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml - -HIDE_STEREOTYPE() - -package "SlicesTestAssembly.SameNamedSubnamespaces" as SlicesTestAssembly.SameNamedSubnamespaces { - package "Billing" as SlicesTestAssembly.SameNamedSubnamespaces.Billing { - package "Domain" as SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain { - [Model] as SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model - } - } - package "Orders" as SlicesTestAssembly.SameNamedSubnamespaces.Orders { - package "Domain" as SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain { - [Model] as SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model - } - } - [Web] as SlicesTestAssembly.SameNamedSubnamespaces.Web -} - -[SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model] --|> [SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model] -[SlicesTestAssembly.SameNamedSubnamespaces.Web] -[#red]> SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain -@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt deleted file mode 100644 index 4d3dc2e23..000000000 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt +++ /dev/null @@ -1,23 +0,0 @@ -@startuml - -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml - -HIDE_STEREOTYPE() - -Boundary(SlicesTestAssembly.SameNamedSubnamespaces, SlicesTestAssembly.SameNamedSubnamespaces) { - Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Billing, Billing) { - Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain, Domain) { - Container(SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model, Model) - } - } - Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Orders, Orders) { - Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain, Domain) { - Container(SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model, Model) - } - } - Container(SlicesTestAssembly.SameNamedSubnamespaces.Web, Web) -} - -[SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model] --|> [SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model] -[SlicesTestAssembly.SameNamedSubnamespaces.Web] -[#red]> SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain -@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt deleted file mode 100644 index 63fde3d69..000000000 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt +++ /dev/null @@ -1,16 +0,0 @@ -@startuml - -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml - -HIDE_STEREOTYPE() - -package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { - [Slice1] as SlicesTestAssembly.MultipleSubnamespaces.Slice1 - [Slice2] as SlicesTestAssembly.MultipleSubnamespaces.Slice2 - [Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3 -} - -[SlicesTestAssembly.MultipleSubnamespaces.Slice1] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice3] -[SlicesTestAssembly.MultipleSubnamespaces.Slice3] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice1] -@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_C4Style.verified.txt new file mode 100644 index 000000000..7acc06996 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_C4Style.verified.txt @@ -0,0 +1,43 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) { + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice1, Slice1) { + Container(Slice1, Slice1) + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service, Service) { + Container(Slice1.Service, Slice1.Service) + } + } + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice2, Slice2) { + Container(Slice2, Slice2) + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service, Service) { + Container(Slice2.Service, Slice2.Service) + } + } + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3, Slice3) { + Container(Slice3, Slice3) + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1, Group1) { + Container(Slice3.Group1, Slice3.Group1) + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner, Inner) { + Container(Slice3.Group1.Inner, Slice3.Group1.Inner) + } + } + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2, Group2) { + Container(Slice3.Group2, Slice3.Group2) + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner, Inner) { + Container(Slice3.Group2.Inner, Slice3.Group2.Inner) + } + } + } +} + +[Slice1] --|> [Slice2] +[Slice1.Service] --|> [Slice2.Service] +[Slice2] --|> [Slice3] +[Slice2.Service] --|> [Slice3] +[Slice3] --|> [Slice1] +[Slice3.Group1] --|> [Slice3] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_CaptureGroupAtStart.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_CaptureGroupAtStart.verified.txt new file mode 100644 index 000000000..eeb8ee985 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_CaptureGroupAtStart.verified.txt @@ -0,0 +1,22 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package "SlicesTestAssembly" as SlicesTestAssembly { + package "DotDotSemantics.Alpha.Service" as SlicesTestAssembly.DotDotSemantics.Alpha.Service { + [SlicesTestAssembly.DotDotSemantics.Alpha] as SlicesTestAssembly.DotDotSemantics.Alpha + } + package "MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1.Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service { + [SlicesTestAssembly.MultipleSubnamespaces.Slice1] as SlicesTestAssembly.MultipleSubnamespaces.Slice1 + } + package "Slice2.Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service { + [SlicesTestAssembly.MultipleSubnamespaces.Slice2] as SlicesTestAssembly.MultipleSubnamespaces.Slice2 + } + } +} + +[SlicesTestAssembly.MultipleSubnamespaces.Slice1] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_DirectCircle.verified.txt similarity index 51% rename from ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt rename to ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_DirectCircle.verified.txt index 1202286f3..08df7321b 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_DirectCircle.verified.txt @@ -5,9 +5,13 @@ HIDE_STEREOTYPE() package "SlicesTestAssembly.DirectCircle" as SlicesTestAssembly.DirectCircle { - [Slice1] as SlicesTestAssembly.DirectCircle.Slice1 - [Slice2] as SlicesTestAssembly.DirectCircle.Slice2 + package "Slice1" as SlicesTestAssembly.DirectCircle.Slice1 { + [Slice1] as Slice1 + } + package "Slice2" as SlicesTestAssembly.DirectCircle.Slice2 { + [Slice2] as Slice2 + } } -[SlicesTestAssembly.DirectCircle.Slice1] <-[#red]> [SlicesTestAssembly.DirectCircle.Slice2] +[Slice1] <-[#red]> [Slice2] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_DoubleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_DoubleAsterisk.verified.txt new file mode 100644 index 000000000..da111c5a5 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_DoubleAsterisk.verified.txt @@ -0,0 +1,43 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { + [Slice1] as Slice1 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service { + [Slice1.Service] as Slice1.Service + } + } + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { + [Slice2] as Slice2 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service { + [Slice2.Service] as Slice2.Service + } + } + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { + [Slice3] as Slice3 + package "Group1" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 { + [Slice3.Group1] as Slice3.Group1 + package "Inner" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner { + [Slice3.Group1.Inner] as Slice3.Group1.Inner + } + } + package "Group2" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 { + [Slice3.Group2] as Slice3.Group2 + package "Inner" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner { + [Slice3.Group2.Inner] as Slice3.Group2.Inner + } + } + } +} + +[Slice1] --|> [Slice2] +[Slice1.Service] --|> [Slice2.Service] +[Slice2] --|> [Slice3] +[Slice2.Service] --|> [Slice3] +[Slice3] --|> [Slice1] +[Slice3.Group1] --|> [Slice3] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_DuplicatePrefixSegment.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_DuplicatePrefixSegment.verified.txt new file mode 100644 index 000000000..4da60c5df --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_DuplicatePrefixSegment.verified.txt @@ -0,0 +1,11 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package "SlicesTestAssembly.DuplicatePrefix.Sub.DuplicatePrefix.Sub" as SlicesTestAssembly.DuplicatePrefix.Sub.DuplicatePrefix.Sub { + [DuplicatePrefix.Sub] as DuplicatePrefix.Sub +} + +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_LimitDependencies.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_LimitDependencies.verified.txt new file mode 100644 index 000000000..7f821f149 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_LimitDependencies.verified.txt @@ -0,0 +1,41 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { + [Slice1] as Slice1 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service { + [Slice1.Service] as Slice1.Service + } + } + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { + [Slice2] as Slice2 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service { + [Slice2.Service] as Slice2.Service + } + } + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { + [Slice3] as Slice3 + package "Group1" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 { + [Slice3.Group1] as Slice3.Group1 + package "Inner" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner { + [Slice3.Group1.Inner] as Slice3.Group1.Inner + } + } + package "Group2" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 { + [Slice3.Group2] as Slice3.Group2 + package "Inner" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner { + [Slice3.Group2.Inner] as Slice3.Group2.Inner + } + } + } +} + +[Slice1] --> [Slice2] +[Slice1.Service] --> [Slice2.Service] +[Slice2] --> [Slice3] +[Slice3] --> [Slice1] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_MultipleCaptureGroups.verified.txt similarity index 51% rename from ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt rename to ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_MultipleCaptureGroups.verified.txt index a050a9c2d..0b106110b 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_MultipleCaptureGroups.verified.txt @@ -6,22 +6,32 @@ HIDE_STEREOTYPE() package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service + [Slice1] as Slice1 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service { + [Slice1.Service] as Slice1.Service + } } package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { - [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service + [Slice2] as Slice2 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service { + [Slice2.Service] as Slice2.Service + } } package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { + [Slice3] as Slice3 package "Group1" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 { - [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner + [Slice3.Group1] as Slice3.Group1 } package "Group2" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 { - [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner + [Slice3.Group2] as Slice3.Group2 } } } -SlicesTestAssembly.MultipleSubnamespaces.Slice1 ..> SlicesTestAssembly.MultipleSubnamespaces.Slice2 -SlicesTestAssembly.MultipleSubnamespaces.Slice2 ..> SlicesTestAssembly.MultipleSubnamespaces.Slice3 -SlicesTestAssembly.MultipleSubnamespaces.Slice3 ..> SlicesTestAssembly.MultipleSubnamespaces.Slice1 +[Slice1] --|> [Slice2] +[Slice1.Service] --|> [Slice2.Service] +[Slice2] --|> [Slice3] +[Slice2.Service] --|> [Slice3] +[Slice3] --|> [Slice1] +[Slice3.Group1] --|> [Slice3] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_NonContiguousCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_NonContiguousCaptureGroups.verified.txt new file mode 100644 index 000000000..0b106110b --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_NonContiguousCaptureGroups.verified.txt @@ -0,0 +1,37 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { + [Slice1] as Slice1 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service { + [Slice1.Service] as Slice1.Service + } + } + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { + [Slice2] as Slice2 + package "Service" as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service { + [Slice2.Service] as Slice2.Service + } + } + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { + [Slice3] as Slice3 + package "Group1" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 { + [Slice3.Group1] as Slice3.Group1 + } + package "Group2" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 { + [Slice3.Group2] as Slice3.Group2 + } + } +} + +[Slice1] --|> [Slice2] +[Slice1.Service] --|> [Slice2.Service] +[Slice2] --|> [Slice3] +[Slice2.Service] --|> [Slice3] +[Slice3] --|> [Slice1] +[Slice3.Group1] --|> [Slice3] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_SameNamedPackages.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_SameNamedPackages.verified.txt new file mode 100644 index 000000000..b60093fe6 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_SameNamedPackages.verified.txt @@ -0,0 +1,28 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package "SlicesTestAssembly.SameNamedSubnamespaces" as SlicesTestAssembly.SameNamedSubnamespaces { + package "Billing.Domain" as SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain { + [Billing.Domain] as Billing.Domain + package "Model" as SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model { + [Billing.Domain.Model] as Billing.Domain.Model + } + } + package "Orders.Domain" as SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain { + [Orders.Domain] as Orders.Domain + package "Model" as SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model { + [Orders.Domain.Model] as Orders.Domain.Model + } + } + package "Web" as SlicesTestAssembly.SameNamedSubnamespaces.Web { + [Web] as Web + } +} + +[Orders.Domain] --|> [Billing.Domain] +[Orders.Domain.Model] --|> [Billing.Domain.Model] +[Web] --|> [Billing.Domain] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_SameNamedPackages_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_SameNamedPackages_C4Style.verified.txt new file mode 100644 index 000000000..69f5c7906 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_SameNamedPackages_C4Style.verified.txt @@ -0,0 +1,28 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +Boundary(SlicesTestAssembly.SameNamedSubnamespaces, SlicesTestAssembly.SameNamedSubnamespaces) { + Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain, Billing.Domain) { + Container(Billing.Domain, Billing.Domain) + Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model, Model) { + Container(Billing.Domain.Model, Billing.Domain.Model) + } + } + Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain, Orders.Domain) { + Container(Orders.Domain, Orders.Domain) + Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model, Model) { + Container(Orders.Domain.Model, Orders.Domain.Model) + } + } + Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Web, Web) { + Container(Web, Web) + } +} + +[Orders.Domain] --|> [Billing.Domain] +[Orders.Domain.Model] --|> [Billing.Domain.Model] +[Web] --|> [Billing.Domain] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_SingleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_SingleAsterisk.verified.txt new file mode 100644 index 000000000..393c2fb54 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesNested_SingleAsterisk.verified.txt @@ -0,0 +1,22 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { + [Slice1] as Slice1 + } + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { + [Slice2] as Slice2 + } + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { + [Slice3] as Slice3 + } +} + +[Slice1] --|> [Slice2] +[Slice2] --|> [Slice3] +[Slice3] --|> [Slice1] +@enduml diff --git a/ArchUnitNETTests/Fluent/PlantUml/PlantUmlFluentComponentDiagramTests.cs b/ArchUnitNETTests/Fluent/PlantUml/PlantUmlFluentComponentDiagramTests.cs index 033fd5683..a18ebfeb7 100644 --- a/ArchUnitNETTests/Fluent/PlantUml/PlantUmlFluentComponentDiagramTests.cs +++ b/ArchUnitNETTests/Fluent/PlantUml/PlantUmlFluentComponentDiagramTests.cs @@ -45,11 +45,14 @@ public void ComponentDiagramFromSlicesTest() Assert.NotEmpty(uml2); var arch1 = new ArchLoader().LoadAssembly(typeof(Architecture).Assembly).Build(); - var sliceRule1 = SliceRuleDefinition - .Slices() - .MatchingWithPackages("ArchUnitNET.(*).(*).(*)"); + var sliceRule1 = SliceRuleDefinition.Slices().Matching("ArchUnitNET.(*).(*).(*)"); const string path = "../../../Fluent/PlantUml/Test.puml"; - var g = new GenerationOptions() { C4Style = true, LimitDependencies = true }; + var g = new GenerationOptions() + { + C4Style = true, + LimitDependencies = true, + NestSlicesInPackages = true, + }; ComponentDiagram() .WithDependenciesFromSlices(sliceRule1.GetObjects(arch1), g) .WriteToFile(path); diff --git a/ArchUnitNETTests/Fluent/Slices/MatchingWithPackagesTests.cs b/ArchUnitNETTests/Fluent/Slices/MatchingWithPackagesTests.cs new file mode 100644 index 000000000..7230472fc --- /dev/null +++ b/ArchUnitNETTests/Fluent/Slices/MatchingWithPackagesTests.cs @@ -0,0 +1,192 @@ +using System; +using System.Linq; +using ArchUnitNET.Domain; +using ArchUnitNET.Domain.PlantUml.Export; +using ArchUnitNET.Fluent; +using ArchUnitNET.Fluent.Slices; +using Xunit; + +// These tests cover the obsolete method itself. +#pragma warning disable CS0618 + +namespace ArchUnitNETTests.Fluent.Slices +{ + /// + /// is obsolete. It creates exactly the + /// slices creates, so its rules behave the same, + /// and the only thing it still does differently is nest its slices in diagrams by default. + /// + public class MatchingWithPackagesTests + { + private static Architecture Architecture => StaticTestArchitectures.SlicesTestArchitecture; + + private const string Root = "SlicesTestAssembly.MultipleSubnamespaces."; + + public static TheoryData Patterns => + new TheoryData + { + Root + "(*)", + Root + "(**)", + Root + "(**)..", + Root + "(*).(*)", + Root + "(*)..(*)", + "..DuplicatePrefix.Sub.(**)", + "(**).Service", + "SlicesTestAssembly.SubnamespaceCircle.(**)", + }; + + [Theory] + [MemberData(nameof(Patterns))] + public void Slices_AreThoseOfMatching(string pattern) + { + var withPackages = SliceRuleDefinition + .Slices() + .MatchingWithPackages(pattern) + .GetObjects(Architecture) + .ToList(); + var matching = SliceRuleDefinition.Slices().Matching(pattern).GetObjects(Architecture); + + Assert.Equal(matching.Select(Describe), withPackages.Select(Describe)); + } + + [Theory] + [MemberData(nameof(Patterns))] + public void Rules_EvaluateLikeThoseOfMatching(string pattern) + { + AssertSameResults( + SliceRuleDefinition.Slices().Matching(pattern).Should().BeFreeOfCycles(), + SliceRuleDefinition.Slices().MatchingWithPackages(pattern).Should().BeFreeOfCycles() + ); + AssertSameResults( + SliceRuleDefinition.Slices().Matching(pattern).Should().NotDependOnEachOther(), + SliceRuleDefinition + .Slices() + .MatchingWithPackages(pattern) + .Should() + .NotDependOnEachOther() + ); + } + + [Fact] + public void BeFreeOfCycles_DetectsCycle() + { + Assert.False( + SliceRuleDefinition + .Slices() + .MatchingWithPackages(Root + "(**)") + .Should() + .BeFreeOfCycles() + .HasNoViolations(Architecture) + ); + } + + // SubnamespaceCircle has real edges (Slice1 -> Slice2, Slice2.Inner -> Slice1) but no + // cycle, so an empty graph is not what makes this pass. + [Fact] + public void BeFreeOfCycles_WhenAcyclic_Passes() + { + Assert.True( + SliceRuleDefinition + .Slices() + .MatchingWithPackages("SlicesTestAssembly.SubnamespaceCircle.(**)") + .Should() + .BeFreeOfCycles() + .HasNoViolations(Architecture) + ); + } + + [Theory] + [MemberData(nameof(Patterns))] + public void Diagram_IsThatOfMatchingNestedInPackages(string pattern) + { + foreach ( + var options in new[] + { + new GenerationOptions(), + new GenerationOptions { C4Style = true }, + new GenerationOptions { LimitDependencies = true }, + } + ) + { + var expected = new PlantUmlFileBuilder() + .WithDependenciesFrom( + SortedSlices(SliceRuleDefinition.Slices().Matching(pattern)), + new GenerationOptions + { + NestSlicesInPackages = true, + C4Style = options.C4Style, + LimitDependencies = options.LimitDependencies, + } + ) + .AsString(); + var actual = new PlantUmlFileBuilder() + .WithDependenciesFrom( + SortedSlices(SliceRuleDefinition.Slices().MatchingWithPackages(pattern)), + options + ) + .AsString(); + + Assert.Equal(expected, actual); + } + } + + [Fact] + public void Diagram_FocusOn_IsThatOfMatchingNestedInPackages() + { + var expected = new PlantUmlFileBuilder() + .WithDependenciesFromFocusOn( + SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)")), + Root + "Slice1", + new GenerationOptions { NestSlicesInPackages = true } + ) + .AsString(); + var actual = new PlantUmlFileBuilder() + .WithDependenciesFromFocusOn( + SortedSlices(SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(**)")), + Root + "Slice1" + ) + .AsString(); + + Assert.Equal(expected, actual); + Assert.Contains("package \"SlicesTestAssembly.MultipleSubnamespaces\"", actual); + } + + private static string Describe(Slice slice) + { + return slice.Description + + ": " + + string.Join( + ", ", + slice + .Types.Select(type => type.FullName) + .OrderBy(n => n, StringComparer.Ordinal) + ); + } + + private static void AssertSameResults(IArchRule matching, IArchRule withPackages) + { + Assert.Equal(matching.Description, withPackages.Description); + Assert.Equal( + matching.Evaluate(Architecture).Select(Summarize), + withPackages.Evaluate(Architecture).Select(Summarize) + ); + } + + private static string Summarize(EvaluationResult result) + { + return result.Passed + + " " + + result.EvaluatedObjectIdentifier.Identifier + + " " + + result.Description; + } + + private static Slice[] SortedSlices(GivenSlices slices) + { + return slices + .GetObjects(Architecture) + .OrderBy(slice => slice.Description, StringComparer.Ordinal) + .ToArray(); + } + } +} diff --git a/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs b/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs index 143d03af5..232cb4a2b 100644 --- a/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs +++ b/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs @@ -27,48 +27,6 @@ public void CycleDetectionTest() .AssertNoViolations(helper); } - // --- Rule evaluation driven by MatchingWithPackages (not just Matching) ------- - - [Fact] - public void BeFreeOfCycles_MatchingWithPackages_DetectsCycle() - { - var helper = new SlicesAssemblyTestHelper().WithoutSnapshot(); - SliceRuleDefinition - .Slices() - .MatchingWithPackages("SlicesTestAssembly.MultipleSubnamespaces.(**)") - .Should() - .BeFreeOfCycles() - .AssertOnlyViolations(helper); - } - - // SubnamespaceCircle has real edges (Slice1 -> Slice2, Slice2.Inner -> Slice1) but no - // cycle, unlike DotDotSemantics whose fixtures have no members and thus no edges at all -- - // an empty graph would pass here even if BeFreeOfCycles always returned "no cycles". - [Fact] - public void BeFreeOfCycles_MatchingWithPackages_WhenAcyclic_Passes() - { - var helper = new SlicesAssemblyTestHelper().WithoutSnapshot(); - SliceRuleDefinition - .Slices() - .MatchingWithPackages("SlicesTestAssembly.SubnamespaceCircle.(**)") - .Should() - .BeFreeOfCycles() - .AssertNoViolations(helper); - } - - [Fact] - public Task NotDependOnEachOther_MatchingWithPackages_ReportsViolations() - { - var helper = new SlicesAssemblyTestHelper(); - var rule = SliceRuleDefinition - .Slices() - .MatchingWithPackages("SlicesTestAssembly.MultipleSubnamespaces.(**)..") - .Should() - .NotDependOnEachOther(); - rule.AssertAnyViolations(helper); - return helper.AssertSnapshotMatches(); - } - [Fact] public void MatchingTest() { @@ -305,21 +263,20 @@ public void Matching_LeadingDotDot_CapturesEveryDepth() // (SlicesTestAssembly.DuplicatePrefix.Sub.DuplicatePrefix.Sub), so a leading ".." pattern // for that prefix has two candidate starting positions to strip from. This pins that // AssignFunc's IndexOf (leftmost match) picks the first occurrence, leaving the second - // "DuplicatePrefix.Sub" -- and the one leading dot "..DuplicatePrefix.Sub." keeps after - // stripping only one of its two literal dots -- in the description. With an absolute - // (non-"..") prefix, the prefix can only ever be found at index 0, so that variant of this - // test would pass even if IndexOf were replaced by a hardcoded 0. + // "DuplicatePrefix.Sub" in the description. With an absolute (non-"..") prefix, the + // prefix can only ever be found at index 0, so that variant of this test would pass even + // if IndexOf were replaced by a hardcoded 0. [Fact] - public void MatchingWithPackages_DuplicatePrefixSegment_UsesFirstOccurrenceAsPrefix() + public void DuplicatePrefixSegment_CapturesAfterTheFirstOccurrenceOfThePrefix() { var slices = SliceRuleDefinition .Slices() - .MatchingWithPackages("..DuplicatePrefix.Sub.(*)") + .Matching("..DuplicatePrefix.Sub.(**)") .GetObjects(StaticTestArchitectures.SlicesTestArchitecture) .ToList(); Assert.Single(slices); - Assert.Equal(".DuplicatePrefix.Sub.DuplicatePrefix.Sub", slices[0].Description); + Assert.Equal("DuplicatePrefix.Sub", slices[0].Description); } // --- ".." semantics ------------------------------------------------------------- diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MatchingWithPackages_ReportsViolations.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MatchingWithPackages_ReportsViolations.verified.txt deleted file mode 100644 index ae856ef18..000000000 --- a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MatchingWithPackages_ReportsViolations.verified.txt +++ /dev/null @@ -1,19 +0,0 @@ -Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**).." should not depend on each other -Result: False -Description: SlicesTestAssembly.MultipleSubnamespaces.Slice1 does depend on other slices: -SlicesTestAssembly.MultipleSubnamespaces.Slice1 -> SlicesTestAssembly.MultipleSubnamespaces.Slice2 - SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class - -Result: True -Description: SlicesTestAssembly.MultipleSubnamespaces.Slice2 does not depend on another slice. -Result: True -Description: SlicesTestAssembly.MultipleSubnamespaces.Slice3 does not depend on another slice. -Message: -"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**).." should not depend on each other" failed: - SlicesTestAssembly.MultipleSubnamespaces.Slice1 does depend on other slices: -SlicesTestAssembly.MultipleSubnamespaces.Slice1 -> SlicesTestAssembly.MultipleSubnamespaces.Slice2 - SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class - - - - diff --git a/TestAssemblies/SlicesTestAssembly/PrefixSiblings/Orders/OrdersClass.cs b/TestAssemblies/SlicesTestAssembly/PrefixSiblings/Orders/OrdersClass.cs new file mode 100644 index 000000000..bee6eea29 --- /dev/null +++ b/TestAssemblies/SlicesTestAssembly/PrefixSiblings/Orders/OrdersClass.cs @@ -0,0 +1,4 @@ +namespace SlicesTestAssembly.PrefixSiblings.Orders; + +// "Orders" is the start of its sibling "OrdersArchive", but not one of its namespace segments. +public class OrdersClass { } diff --git a/TestAssemblies/SlicesTestAssembly/PrefixSiblings/OrdersArchive/OrdersArchiveClass.cs b/TestAssemblies/SlicesTestAssembly/PrefixSiblings/OrdersArchive/OrdersArchiveClass.cs new file mode 100644 index 000000000..500d6043c --- /dev/null +++ b/TestAssemblies/SlicesTestAssembly/PrefixSiblings/OrdersArchive/OrdersArchiveClass.cs @@ -0,0 +1,8 @@ +using SlicesTestAssembly.PrefixSiblings.Orders; + +namespace SlicesTestAssembly.PrefixSiblings.OrdersArchive; + +public class OrdersArchiveClass +{ + public OrdersClass Reference = null!; +} diff --git a/documentation/docs/guide.md b/documentation/docs/guide.md index 022bf60d9..ed193526d 100644 --- a/documentation/docs/guide.md +++ b/documentation/docs/guide.md @@ -239,28 +239,29 @@ PlantUmlDefinition.ComponentDiagram().WithDependenciesFromSlices(sliceRule.GetOb ``` ### 5.2 Structured diagram dependencies -On the previous case there are too many dependencies, this option is suitable mainly for small projects / architectures / slices. With an increase in the number of objects, it makes sense to introduce another type of display - a display with packages. It differs from the previous case only with the modified creation of SliceRule. +On the previous case there are too many dependencies, this option is suitable mainly for small projects / architectures / slices. With an increase in the number of objects, it makes sense to introduce another type of display - a display with packages. It differs from the previous case only in the `NestSlicesInPackages` option, which draws the namespaces as `package` blocks and each slice in the package of the deepest namespace that contains all of its types. A package that holds no slice and only one other package is merged with it, e.g. into `Orders.Domain`. A slice whose types share no namespace is drawn outside any package. This applies to every slice, also to those of `SlicedBy` and of custom assignments. ![Diagram](diagrams/archUnitNet_all_withPackages.svg) ```cs string pattern = "ArchUnitNET.(**)"; -GivenSlices sliceRule = SliceRuleDefinition.Slices().MatchingWithPackages(pattern); +GivenSlices sliceRule = SliceRuleDefinition.Slices().Matching(pattern); Architecture arch = new ArchLoader().LoadAssembly(typeof(ArchUnitNET.Domain.Architecture).Assembly).Build(); +GenerationOptions g = new GenerationOptions(){NestSlicesInPackages = true}; string path = "diagram.puml"; -PlantUmlDefinition.ComponentDiagram().WithDependenciesFromSlices(sliceRule.GetObjects(arch)).WriteToFile(path); +PlantUmlDefinition.ComponentDiagram().WithDependenciesFromSlices(sliceRule.GetObjects(arch), g).WriteToFile(path); ``` ### 5.3 LimitDependencies-mod -The previous case still shows a large number of connections. For maximum simplification, as well as demonstrating the overall picture, it makes sense to reformat it into a limited-dependencies version. This kind of display shows dependencies between packages at the same slice level. +The previous case still shows a large number of connections. For maximum simplification, as well as demonstrating the overall picture, it makes sense to reformat it into a limited-dependencies version. This kind of display shows only dependencies between slices at the same depth, i.e. whose types share an equally deep namespace, nested or not. ![Diagram](diagrams/archUnitNet_all_compact.svg) ```cs string pattern = "ArchUnitNET.(**)"; -GivenSlices sliceRule = SliceRuleDefinition.Slices().MatchingWithPackages(pattern); +GivenSlices sliceRule = SliceRuleDefinition.Slices().Matching(pattern); Architecture arch = new ArchLoader().LoadAssembly(typeof(ArchUnitNET.Domain.Architecture).Assembly).Build(); -GenerationOptions g = new GenerationOptions(){LimitDependencies = true}; +GenerationOptions g = new GenerationOptions(){NestSlicesInPackages = true, LimitDependencies = true}; string path = "diagram.puml"; @@ -298,9 +299,9 @@ To enable an C4plantUML-style view of diagrams, set in the GenerationOptions fla ![Diagram](diagrams/archUnitNet_three_alternative.svg) ```cs string pattern = "ArchUnitNET.(*).(*).(*)"; -GivenSlices sliceRule = SliceRuleDefinition.Slices().MatchingWithPackages(pattern); +GivenSlices sliceRule = SliceRuleDefinition.Slices().Matching(pattern); Architecture arch = new ArchLoader().LoadAssembly(typeof(ArchUnitNET.Domain.Architecture).Assembly).Build(); -GenerationOptions g = new GenerationOptions(){C4Style = true}; +GenerationOptions g = new GenerationOptions(){NestSlicesInPackages = true, C4Style = true}; string path = "diagram.puml"; @@ -308,20 +309,32 @@ PlantUmlDefinition.ComponentDiagram().WithDependenciesFromSlices(sliceRule.GetOb ``` ### 5.6 Focus-mod -Focus mod allows you to show all dependencies on the selected package or out of the package. +Focus mod allows you to show all dependencies on the selected package or out of the package. The package to focus on is a namespace. A slice is focused when all of its types lie in it, flat or nested; slices that depend on it or that it depends on are drawn as well. ![Diagram](diagrams/archUnitNet_focusOn.svg) ```cs string pattern = "ArchUnitNET.(**)"; -string focusOnThisPackage = "ArchUnitNET.Fluent.Syntax.Elements" -GivenSlices sliceRule = SliceRuleDefinition.Slices().MatchingWithPackages(pattern); +string focusOnThisPackage = "ArchUnitNET.Fluent.Syntax.Elements"; +GivenSlices sliceRule = SliceRuleDefinition.Slices().Matching(pattern); Architecture arch = new ArchLoader().LoadAssembly(typeof(ArchUnitNET.Domain.Architecture).Assembly).Build(); +GenerationOptions g = new GenerationOptions(){NestSlicesInPackages = true}; string path = "diagram.puml"; -PlantUmlDefinition.ComponentDiagram().WithDependenciesFromSlices(sliceRule.GetObjects(arch), focusOnThisPackage).WriteToFile(path); +PlantUmlDefinition.ComponentDiagram().WithDependenciesFromSlices(sliceRule.GetObjects(arch), focusOnThisPackage, g).WriteToFile(path); ``` +### 5.7 Migrating from MatchingWithPackages +`MatchingWithPackages` is obsolete and will be removed in a future release. It creates the same slices +as `Matching`, with the same names and violations, and only nests them in diagrams. Replace +`MatchingWithPackages(pattern)` with `Matching(pattern)` and draw the slices with +`new GenerationOptions { NestSlicesInPackages = true }`. Diagrams change too: a slice is a box +inside its namespace instead of being drawn as that namespace. + +Failure messages of former `MatchingWithPackages` rules no longer start a slice's name with the +namespace before the first capture group, and their frozen violations, which were stored under those +longer names, have to be frozen again once. + ## 6. Further Reading and Examples A complete overview of all available methods can be found [here](additional.md).