Skip to content

Nest slices in packages via GenerationOptions, deprecate MatchingWithPackages - #513

Open
alexanderlinne wants to merge 1 commit into
stop-deriving-namespace-from-slicefrom
nest-slices-in-packages-option
Open

alexanderlinne wants to merge 1 commit into
stop-deriving-namespace-from-slicefrom
nest-slices-in-packages-option

Conversation

@alexanderlinne

@alexanderlinne alexanderlinne commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Why MatchingWithPackages existed, and why it no longer has to

On main, a slice carries a single string, its name, plus an optional NameSpace. The PlantUML exporter works out everything from those strings:

  • which slice is a package of another (StartsWith),
  • which arrows to merge (Contains),
  • how deep LimitDependencies goes (counting dots).

For that it needs fully qualified names such as App.Orders.Web. Rules need the opposite: short names for failure messages and freeze stores. Both requirements apply to the same string, so the choice has to be made when the slices are built, which is why there are two methods:

  • Matching produces short names and draws flat.
  • MatchingWithPackages produces prefix + name and draws nested.

The prefix is the literal pattern text before the capture group, so both methods group types the same way; only the names differ. In other words, a diagram option ends up in failure messages and frozen violations.

The exporter doesn't need anything from the pattern: it nests each slice in the deepest namespace its types share. So the flat vs. nested choice moves from slice creation to rendering, and one set of slices can be checked and drawn both ways.

This PR doesn't touch the matcher; it runs on main's. Since the layout depends only on the types, the next two PRs can change naming and matching without touching it.

What changes

  • GenerationOptions.NestSlicesInPackages (default false) draws the namespace tree as package blocks.

  • MatchingWithPackages is [Obsolete] and creates exactly Matching's slices, so rule descriptions, slices, names, violations and freeze keys are the same as with Matching. Its slices still nest by default. MatchingWithPackagesTests checks that both methods give the same slices, the same rule results and the same diagrams.

  • Slices are boxes inside their namespaces. With nesting on, every slice is drawn under its name 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, top-down at every level, e.g. into Orders.Domain.
    • A slice whose types share no namespace is drawn outside any package.
    • This applies to every slice, also to SlicedBy slices and custom SliceAssignments.
    • No slice is drawn as a package any more, and there are no package arrows; arrows are the flat ones.
    pr2-1-nested-diagram
  • Packages come from the types, not from the pattern text. With a leading .., MatchingWithPackages nested slices under the pattern text with one of its dots stripped. ..DuplicatePrefix.Sub.(**) thus got a package .DuplicatePrefix.Sub, which doesn't exist; the slice now sits in the namespace its types are in:

    pr2-2-duplicate-prefix
  • A slice whose name is a prefix of another's is drawn. On main, with App.(*) over App.Order and App.Orders, "Orders".StartsWith("Order"), so Order was taken for the package of Orders and drawn neither as a slice nor with its dependency on Billing. That check is gone along with package-slices:

    pr2-3-is-package-name-prefix
  • Focus mode takes a namespace. WithDependenciesFromFocusOn and the fluent WithDependenciesFromSlices(slices, focusOnPackage) take an optional GenerationOptions for the nesting. A slice is focused when all of its types lie in the given namespace or below it, matched by segments; the slices it depends on, or that depend on it, are drawn as well. On main, the focus string was a substring of the slice names, so flat diagrams were focused with a bare name ("Slice1"), which also matched Slice10. Now flat and nested diagrams both take the full namespace and focus the same slices.

  • LimitDependencies keeps arrows between slices at the same depth, i.e. whose types share an equally deep namespace, flat or nested. For (**) patterns that is exactly main's flat output. Nested, main drew a sibling overview with package arrows instead; it now keeps the same arrows as flat, inside the tree. The exporter decides which arrows to draw, so OneToOneCompact no longer checks dots when it renders. The namespace overload filters the same way, and its output doesn't change; a new snapshot pins it.

  • Guide. Sections 5.2–5.6 use Matching with NestSlicesInPackages and describe the new layout, focus and depth rules, and a new section 5.7 explains the migration. 5.6 also gets a missing semicolon.

Every flat snapshot is unchanged. The nested snapshots were renamed (BuildUmlBySlicesMatchingWithPackages_* → BuildUmlBySlicesNested_*), switched to the option and regenerated.

Breaking changes

  • MatchingWithPackages is obsolete. It creates Matching's slices, so its failure messages and frozen violations no longer use names qualified with the namespace before the capture group. Frozen violations of MatchingWithPackages rules have to be frozen again once.
  • Slice.NameSpace, Slice.ContainsNamespace() and SliceIdentifier.NameSpace are removed, and SliceIdentifier.Of loses its nameSpace parameter.
  • Nested slice diagrams change: every slice is a box at its types' common namespace; no slice is drawn as a package and there are no package arrows.
  • Focus mode takes a namespace, also for flat diagrams, and matches it by segments.
  • Nested LimitDependencies keeps same-depth arrows instead of a sibling overview.
  • OneToOneCompact renders whatever the dots in its names; the exporter filters instead.

Migration: replace MatchingWithPackages(pattern) with Matching(pattern) and draw the slices with new GenerationOptions { NestSlicesInPackages = true }. Pass the full namespace to focus mode.

How the images were made

All images render PlantUML that the exporter produced. The "before" sides were produced on main (#512 doesn't touch slice diagrams), the "after" sides at this PR.

  • Image 1 shows the .verified.txt snapshots of …MultipleSubnamespaces.(**) on either side.
  • The "before" side of image 2 was produced with a throwaway test, because main has no snapshot for it; the "after" side is BuildUmlBySlicesNested_DuplicatePrefixSegment.
  • Image 3 renders a small fixture that isn't part of this PR (App.Order, App.Orders, App.Billing).

They were rendered with PlantUML 1.2025.4 and Graphviz, with the remote !include of C4-PlantUML replaced by PlantUML's bundled copy (!include <C4/C4_Container>).

@alexanderlinne
alexanderlinne added this pull request to stack #514 October 24, 2026 15:57
@codecov-commenter

codecov-commenter commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.63033% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.36%. Comparing base (bf4efd7) to head (8a78006).

Files with missing lines Patch % Lines
...tNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs 98.14% 0 Missing and 3 partials ⚠️
...ntUml/PlantUmlFluentComponentDiagramInitializer.cs 0.00% 2 Missing ⚠️
Additional details and impacted files
@@                          Coverage Diff                           @@
##           stop-deriving-namespace-from-slice     #513      +/-   ##
======================================================================
- Coverage                               86.53%   86.36%   -0.17%     
======================================================================
  Files                                     261      261              
  Lines                                   12713    12623      -90     
  Branches                                 1241     1226      -15     
======================================================================
- Hits                                    11001    10902      -99     
- Misses                                   1366     1374       +8     
- Partials                                  346      347       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alexanderlinne
alexanderlinne force-pushed the nest-slices-in-packages-option branch from d38ef64 to e8f8233 Compare October 24, 2026 20:08
@alexanderlinne
alexanderlinne marked this pull request as draft October 25, 2026 19:39
@alexanderlinne
alexanderlinne force-pushed the nest-slices-in-packages-option branch from e8f8233 to 8a2c09b Compare October 28, 2026 19:54
@alexanderlinne
alexanderlinne marked this pull request as ready for review October 29, 2026 21:31
…ngWithPackages

MatchingWithPackages existed because a slice carried nothing but its
name: the exporter worked out the nesting from that string, so it had to
be fully qualified, while rules needed short names. The choice between
the two had to be made when the slices were created, and the qualified
names ended up in failure messages and frozen-rule stores as well.

Both methods group types the same way: the namespace before the capture
group is literal pattern text, so prefixing every name with it changes
no grouping. Diagrams need no data from the pattern: the exporter nests
every slice in the namespaces its types share.

GenerationOptions.NestSlicesInPackages (default false) makes the
exporter draw the namespace tree as packages and put each slice, under
its name, 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, 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. Focus mode takes
the options too.

MatchingWithPackages is obsolete. It creates the same slices as
Matching, so its rules report the same names and violations. It only
still nests its slices in diagrams by default; they are now boxes inside
their namespaces rather than the namespaces themselves, and package
arrows are gone.

Packages now come from the types' namespaces, so
"..DuplicatePrefix.Sub.(**)" no longer gets a package
".DuplicatePrefix.Sub.".

Slices whose name is a prefix of another's, such as App.Order and
App.Orders, are no longer mistaken for a package and dropped from the
diagram.

Focus mode focuses the slices whose types all lie in the given
namespace, and draws the slices they depend on or that depend on them.
LimitDependencies keeps the arrows between slices whose common
namespaces are equally deep. Both rules now use the types rather than
the diagram names, so they work the same flat and nested.

The guide shows Matching with NestSlicesInPackages in its diagram
examples and gains a migration section.

Breaking changes:
- MatchingWithPackages is obsolete, and creates Matching's slices, so
  its failure messages and frozen violations no longer use names
  qualified with the namespace before the capture group.
- Slice.NameSpace, Slice.ContainsNamespace() and
  SliceIdentifier.NameSpace are removed, and SliceIdentifier.Of loses
  its nameSpace parameter.
- Nested slice diagrams change: every slice is a box at its types'
  common namespace; no slice is drawn as a package and there are no
  package arrows.
- Focus mode takes a namespace, also for flat diagrams, and matches it
  by segments.
- Nested LimitDependencies keeps same-depth arrows instead of a sibling
  overview.
- OneToOneCompact renders whatever the dots in its names; the exporter
  filters instead.

Signed-off-by: Alexander Linne <alexander.linne@tngtech.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants