Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: TNG/ArchUnitNET
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: TNG/ArchUnitNET
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: refactor-slice-implemenation
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 10 commits
  • 75 files changed
  • 1 contributor

Commits on Aug 14, 2026

  1. test: move slice fixtures into a dedicated SlicesTestAssembly

    The slice tests shared TestAssembly with the rest of the suite, which meant
    their namespace layout could not be changed without disturbing unrelated
    tests, and it offered no way to express the namespace shapes slice patterns
    need to be tested against.
    
    Add TestAssemblies/SlicesTestAssembly, mirroring the other purpose-built test
    assemblies, with one folder per scenario:
    
      DirectCircle        two slices depending on each other
      SubnamespaceCircle  a cycle that only appears when sub-namespaces are folded
                          into their parent slice (see #208)
      MultipleSubnamespaces  slices nested up to three levels deep
      DuplicatePrefix     a namespace whose last segment repeats its parent
      DotDotSemantics     namespace shapes that distinguish a ".." matching whole
                          segments from one matching within a segment
    
    No production code changes. The expectations in SlicesTests are the current
    implementation's, adjusted only for the new fixture: MultipleSubnamespaces has
    nine namespaces where TestAssembly.Slices had seven, and Slice3 now has four
    sub-namespaces rather than two.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    786b4d7 View commit details
    Browse the repository at this point in the history
  2. test: pin current slice rule and PlantUML diagram behaviour

    The slice pattern matcher is about to be reimplemented. Before touching it,
    capture what it does today so that each following commit shows its own effect
    as a test diff rather than leaving the reader to infer it.
    
    No production code changes; the expectations below are the current behaviour,
    warts included:
    
      - Everything after the first "(*" is discarded, so "(*)", "(*)..", "(*).(*)"
        and "(*)..(*)" all produce the same nine slices, a trailing literal such as
        "(**).Service.." matches nothing, and alternation is not supported at all.
      - A leading ".." is likewise ignored: the prefix is matched with Contains.
      - ".." may match within a namespace segment, so "(*)..Service" matches the
        single segment "AlphaService" as well as "Alpha.Service".
      - Patterns are validated per type while the slices are enumerated, so an
        invalid pattern raises nothing until the result is consumed.
      - Matching (unlike MatchingWithPackages) leaves a slice without a namespace
        prefix, which PlantUmlSlice.BuildStringC4Style dereferences unconditionally
        -- pinned as the NullReferenceException it currently throws.
    
    PlantUmlSliceDiagramSnapshotTests now covers every combination of pattern and
    rendering mode: plain and with packages, single and double asterisk, multiple
    and non-contiguous capture groups, LimitDependencies, C4Style and focus-on.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    2d6be8a View commit details
    Browse the repository at this point in the history
  3. feat: match slice patterns with regex like ArchUnit

    Slice patterns were matched by string surgery: the pattern was split at
    the first "(*", and everything after it was discarded and replaced with
    "(**).". Every pattern therefore behaved as if it ended in a greedy
    capture, so "(*)", "(*)..", "(*).(*)" and "(*)..(*)" all produced the
    same nine slices, a trailing literal such as "(**).Service.." matched
    nothing, and alternation was ignored outright.
    
    Convert the pattern to a regular expression instead, porting
    PackageMatcher.convertToRegex from ArchUnit. "(*)" now captures exactly
    one namespace segment, "(**)" one or more, "*" matches a segment without
    capturing it, ".." skips segments, and "[A|B]" alternates.
    
    Pattern validation moves from the per-type assignment callback to
    Matching/MatchingWithPackages, so a malformed pattern is rejected where
    it is written rather than on the first type that happens to be checked.
    The "must contain (*) or (**)" check is deliberately last: a pattern
    that is malformed *and* has no capture group should report the specific
    problem.
    
    Only the first capture group names a slice for now; patterns with
    several groups are completed in a later commit.
    
    Visible in the tests:
    
    - "SubnamespaceCircle.(*).." now folds Slice2.Inner into Slice2 and so
      reports the cycle Slice1 -> Slice2 -> Slice1 (issue #208). It used to
      give Inner a slice of its own and see no cycle.
    - "MultipleSubnamespaces.(**).." no longer drops the top-level classes,
      so it now sees the Slice3 -> Slice1 edge that closes the loop.
    - ".." can still match empty in the middle of a segment, which splits
      "AlphaService" into "AlphaServic" + "e" and merges "Alpha.Service"
      with "AlphaService". The regex is ported faithfully, including this
      defect; it is pinned in DotDot_* and fixed separately.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    78f6fa2 View commit details
    Browse the repository at this point in the history
  4. refactor: remove CountOfAsteriskInPattern from Slice and SliceIdentifier

    The old string-surgery matcher could produce slice names containing dots
    even for a pattern made only of "(*)" groups, because it discarded
    everything after the first group and matched greedily. PlantUmlFileBuilder
    compensated with RemovePatternInappropriateSlices, which counted the dots
    in a slice name and dropped every slice with at least as many dots as the
    pattern had "(*)" groups.
    
    Now that "(*)" expands to \w+, a captured part can never contain a dot, so
    that filter can no longer remove anything: the dot count of a slice name
    minus the dot count of its namespace prefix is always zero, and the
    pattern always has at least one group. Remove the filter along with the
    CountOfAsteriskInPattern it consumed.
    
    BREAKING CHANGE: Slice.CountOfAsteriskInPattern,
    SliceIdentifier.CountOfAsteriskInPattern and the countOfAsteriskInPattern
    parameter of SliceIdentifier.Of are gone. Callers of SliceIdentifier.Of
    that passed a namespace positionally need to drop the middle argument.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    b58b016 View commit details
    Browse the repository at this point in the history
  5. fix: base Slice equality on the identifier alone

    Slice.Equals also compared Types, and IEnumerable<IType> has reference
    equality, so two slices built from the same identifier over equal but
    distinct sequences compared as different. A slice is defined by its
    identifier -- the types are derived from it -- so comparing them adds
    nothing but the reference-identity trap.
    
    This matters because PlantUmlFileBuilder calls slices.Distinct() and the
    freeze store deduplicates violations by slice.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    8419132 View commit details
    Browse the repository at this point in the history
  6. feat: support multiple capture groups in slice patterns

    Only the first capture group named a slice, so "App.(*).(*)" grouped
    "App.Foo.Bar" and "App.Foo.Baz" into a single slice called "Foo" and the
    second group was silently ignored.
    
    Keep every captured group instead. SliceIdentifier now holds the parts as
    a list and compares them element-wise, so two identifiers belong to the
    same slice exactly when they captured the same parts in the same order --
    "Foo" (one part) and ["Foo", "Bar"] are different slices even though the
    first parts match.
    
    The parts are joined with "." into a single name. A slice has to have one
    name, not two: PlantUML component aliases may not contain whitespace, and
    the exporter splits the name on "." to nest package blocks, so anything
    else would leave failure messages, the freeze store and the diagrams
    disagreeing about what a slice is called. Dots are also what "(**)"
    already produces, so "App.(*).(*)" and "App.(**)" now name the same
    grouping the same way.
    
    Note that upstream ArchUnit joins with " - " instead. It can afford to
    because it offers namingSlices("$1.$2") to override the default;
    ArchUnitNET has no such API yet, so its default has to stand on its own.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    75253a3 View commit details
    Browse the repository at this point in the history
  7. fix: make .. in slice patterns match whole namespace segments

    ".." is documented as "zero or more namespace segments", but the regex
    ported from ArchUnit's PackageMatcher.TWO_DOTS_REGEX,
    
        (?:(?:^\w*)?\.(?:\w+\.)*(?:\w*$)?)?
    
    is optional as a whole and brings its own dots, so it also matches the
    empty string in the middle of a segment. Three consequences:
    
      App.(*)..(*)   on App.Slice3    captured ["Slice", "3"]
      App.(*)..Serv  on App.MyServ    captured ["My"]
      App.(*)..(*)   on App.Single    captured ["Singl", "e"]
    
    None of those are namespace segments; the pattern silently invented a
    boundary inside a name.
    
    Expand ".." depending on where it sits in the pattern instead, so the
    separating dot is always consumed:
    
      leading    (?:\w+\.)*      skip whole leading segments
      trailing   (?:\.\w+)*      skip whole trailing segments
      inner      \.(?:\w+\.)*    the separating dot, then whole segments
    
    The expansion stays greedy, matching the behaviour released so far for
    every single-capture pattern; only the mid-segment matches disappear.
    
    The deviation from upstream is recorded on the method.
    
    Visible in the tests: the DotDotSemantics fixture stops splitting
    "AlphaService" and "Single", and the non-contiguous PlantUML diagrams lose
    their bogus "Slice.1"/"Slice.2"/"Slice.3" components.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    7b5cb17 View commit details
    Browse the repository at this point in the history
  8. fix: do not nest PlantUML packages for slices that are not namespace …

    …paths
    
    PlantUmlSlice splits a slice name on "." to build nested package blocks,
    which only tells the truth when the name really is the namespace path of
    everything inside the slice. A pattern with ".." between two capture
    groups drops the segments in between: "App.(*)..(*)" names the types in
    "App.Slice3.Group1.Inner" "App.Slice3.Inner", and the diagram then drew
    "Inner" as a sibling of "Group1" -- inverting a containment relation the
    same diagram shows correctly one level up, and claiming a namespace that
    does not exist.
    
    Check that every type in the slice actually lives at or below the slice
    name before nesting it, and draw it as one flat, fully qualified
    component otherwise. The slice's own NameSpace is untouched, so IsPackage,
    the focus-on checks and the LimitDependencies branch are unaffected.
    
    Three defects on paths this newly exercises, all older than this branch:
    
    - PlantUmlSlice.BuildStringC4Style had no return in its "no namespace"
      branch and dereferenced the namespace on the next line. Any C4Style
      diagram built from Matching (rather than MatchingWithPackages) slices
      threw a NullReferenceException. The test that pinned the throw becomes
      a real snapshot.
    - A capture group at position 0 (e.g. MatchingWithPackages("(*)..")) gave
      the slice an empty namespace rather than none, and BuildString then
      called "".Remove(-1). Report no namespace instead.
    - OneToOneIfSameParentNamespace wrote component names bare where every
      other arrow brackets them, so "[a] --|> [b]" came out as "a --|> b".
      That happens to parse while a name is a plain identifier and stops as
      soon as one is not. Its two mixed component/package arrows had the same
      problem and no test at all; both are now covered.
    
    Also fills the last hole in the diagram matrix, LimitDependencies over a
    non-contiguous pattern.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    e05db0a View commit details
    Browse the repository at this point in the history
  9. feat: name slices after the pattern text between their capture groups

    Joining the captured parts with "." made a slice name read as a namespace
    even when it was not one. "App.(*)..(*)" named the types in
    "App.Slice3.Group1.Inner" the slice "Slice3.Inner", which is a namespace
    that does not exist, and "App.(*).Service.(*)" dropped the "Service"
    segment from "Orders.Service.Http" and called the slice "Orders.Http".
    The exporter already refuses to nest such names, but a rule message or a
    freeze entry showed them with nothing to say they were not paths.
    
    Build the name from the pattern instead: take the pattern text from the
    first capture group to the last and substitute what each group captured.
    The separators come from the pattern rather than from the match, so every
    type a pattern groups together still gets the same name.
    
      App.(*).(*)          Orders.Http        unchanged
      App.(*).Service.(*)  Orders.Service.Http   was Orders.Http
      App.(*)..(*)         Orders..Http          was Orders.Http
      App.(*).*.(*)        Orders.*.Http         was Orders.Http
    
    Text before the first group and after the last one selects which types
    belong to a slice rather than naming it, so it stays out -- that is what
    keeps "App.(*)" naming a slice "Orders" and not "App.Orders", and it means
    every single-capture pattern keeps exactly the name it has today.
    
    Alternations become "*": a PlantUML name may not contain "[" or "]", and
    which alternative matched is no more part of a slice's identity than which
    segment "*" matched.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    12b1926 View commit details
    Browse the repository at this point in the history
  10. docs: document slice rules and pattern syntax

    The slice API had almost no XML documentation, and the guide covered
    slices only as a one-paragraph "Cycle Rule" that did not say what a
    pattern may contain or what a slice ends up being called.
    
    Document every public type under Fluent/Slices and the two domain types,
    and rewrite guide.md 3.6 as "Slice Rules" with the pattern token table,
    the naming rules and the two available conditions. Note in 5.4 which
    patterns nest in a diagram and which do not.
    
    No behaviour change; the only non-comment edit is a missing semicolon in
    the focus-on example in 5.6.
    alexanderlinne committed Aug 14, 2026
    Configuration menu
    Copy the full SHA
    06d08a4 View commit details
    Browse the repository at this point in the history
Loading