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.

```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.

```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

```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.

```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).