Skip to content

0.13.5

Latest

Choose a tag to compare

@alexanderlinne alexanderlinne released this 26 Oct 18:07
f439e0d

Most of this release fixes cases where ArchUnitNET missed dependencies, crashed while loading or evaluating, or drew a wrong diagram. Dependencies inside async methods are now found in Release builds and in async lambdas, so rules that only passed because those dependencies were missing may start failing. Please read Upgrading before you update. Also in this release: ResideInNamespaceOrBelow and NotBeNestedIn, and much better PlantUML export for slices.

Highlights

  • Dependencies in async code are no longer lost. In optimized builds, every dependency inside an async method body used to be missing, and so were the dependencies inside async lambdas. Negative rules such as NotDependOnAny could therefore pass without checking anything.
  • Combined rules are now consistent. a.And(b) and b.And(a) now give the same result, and RequirePositiveResults is respected on both sides.
  • Slice diagrams are correct for more namespace layouts. Packages with the same name under different parents are no longer merged. Slice names that aren't valid PlantUML ids (like nested or generic type names) no longer produce broken diagrams, and C4 style works for slices without a namespace.

Added

  • ResideInNamespaceOrBelow and its negated forms. These match a namespace and all of its sub-namespaces, so a rule no longer needs to list every sub-namespace or use a regular expression like ^Foo\.Bar(\.|$). "Foo.Bar" matches Foo.Bar and Foo.Bar.Baz, but not Foo.BarBaz, and the comparison ignores case like ResideInNamespace does. Available as ResideInNamespaceOrBelow / NotResideInNamespaceOrBelow on conditions and ResideInNamespaceOrBelow / DoNotResideInNamespaceOrBelow on predicates. #511 (@StefanGreve)

    Types().That().ResideInNamespaceOrBelow("MyProject.Model")
        .Should().NotBePublic()
  • NotBeNestedIn / AreNotNestedIn. These are the negated forms of BeNestedIn / AreNestedIn, with the same overloads. #487 (@alexanderlinne)

Fixed

Loading and dependency analysis

  • Async methods in Release builds. In optimized builds the compiler creates async state machines as structs, which the loader didn't recognize. It then analyzed only the small method that starts the state machine and lost every call, new and field access inside the method body. The loader now reads the state machine from AsyncStateMachineAttribute / IteratorStateMachineAttribute. #500 (@iamAdarshh), fixes #498
  • Async lambdas. Dependencies inside async lambdas, e.g. Register(async () => C3.Bar()), are now added to the method that contains the lambda. #505 (@mkay-sh), fixes #230
  • Assemblies that can't be resolved. ArchLoader.Build() threw AssemblyResolutionException (for example for mscorlib in .NET Framework 4.8 SDK-style projects) when a referenced assembly couldn't be found. Types from such assemblies are now treated as unavailable, as intended. #502 (@mak638), fixes #497
  • Types without a namespace or assembly. Namespace and assembly predicates and conditions threw a NullReferenceException on Types(true) when the architecture contained function pointer types. Such types now simply don't reside in any namespace or assembly. #501 (@iamAdarshh), fixes #492

Rules

  • And / Or are commutative. A combined rule was built from the first rule's rule creator rather than from the rule itself. As a result, a rule whose predicate matched nothing counted as failing only when it was on the right-hand side of And / Or. HasNoViolations() and Evaluate() now also agree when a predicate matches nothing, and a combined rule now uses the inner rule's RequirePositiveResults instead of its own. #504 (@brandhuf)
  • AreStatic / AreNotStatic on members whose static state is unknown now behave the same as BeStatic / NotBeStatic. #485 (@alexanderlinne)
  • Interfaces().That()...As() / .Because() now return GivenInterfacesConjunctionWithDescription, like the other rule types. #486 (@alexanderlinne)

Slices and PlantUML export

  • Packages with the same name under different parents stay separate. PlantUML identifies a package by its name, so Orders.Domain and Billing.Domain were drawn as one Domain package, and an arrow to one of them could end in the other. Each package is now declared with its full path as its alias (package "Domain" as App.Billing.Domain) and keeps its own name as its label. #499 (@alexanderlinne)
  • Nested slices are written as one package tree. Before, every shared parent package was reopened once per slice. The .puml output is now shorter, indented by nesting level, and reads like a hand-written diagram. Hyperlinks on nested slices, which PlantUML used to reject, now work. #499 (@alexanderlinne)
  • Slice names that aren't valid PlantUML ids (for example Outer+Inner or List`1, which SlicedBy produces from type names) now get an id of their own. The name is still used as the label. #510 (@alexanderlinne)
  • C4 style works for slices without a namespace. Slices().Matching(...) with GenerationOptions.C4Style threw a NullReferenceException. Patterns that start with a capture group, such as MatchingWithPackages("(**).Service"), crashed the exporter in the same way. #495, #509 (@alexanderlinne)
  • OneToOneIfSameParentNamespace arrows now put component names in brackets like every other arrow type, so names that contain .. or * no longer break the diagram. #508 (@alexanderlinne)
  • Slice equality depends only on the slice's identifier. Two slices with the same identifier used to compare as different when their type lists were separate instances. This affected the deduplication of slices in diagrams and of violations in frozen rules. #507 (@alexanderlinne)

Upgrading

No changes to the fluent API are required. The following fixes can still change the results of existing tests:

  • Rules may find new violations. Dependencies inside async methods (in Release builds) and inside async lambdas are now part of the architecture. A rule that passed because they were missing will now report them. This most likely affects test suites that run in -c Release. Note that it is still recommended to run ArchUnitNET tests with -c Debug in order to have all features available.
  • Combined rules may fail where they passed before. If one side of an And / Or matches no objects, the combined rule now fails, unless that side uses WithoutRequiringPositiveResults(). Before, this depended on the order of the two rules.
  • The generated PlantUML text for slices has changed. It now uses package aliases, generated ids and indentation, and empty packages are drawn as package boxes. The rendered diagrams are otherwise unchanged. If you compare generated .puml files with saved copies, update the saved copies.
  • Removed HaveAnyAttributesWithArguments(object, params object[]) predicate overload. This leftover was missed when the other params object[] overloads were removed in 0.13.0. Pass a collection instead: HaveAnyAttributesWithArguments(new object[] { a, b }). #486
  • PropertyMemberPredicateDefinition was renamed to PropertyMemberPredicatesDefinition to match the other definition classes. This only matters if you use the class directly instead of the fluent API. #489

Testing and CI

  • Large expansion and cleanup of the fluent syntax and slice test suites. Snapshot output is now independent of loader order, and the assert helpers can no longer pass without checking anything. #484, #486, #488, #489, #490, #495 (@alexanderlinne)
  • NuGet packages are now published with NuGet trusted publishing instead of an API key. #483 (@alexanderlinne)
  • Development tools are pinned with mise, and hk runs formatting and lint checks (csharpier, actionlint, gitleaks, zizmor) through mise run check. Also includes security fixes in the GitHub Actions workflows and removal of the old Travis configuration. #482 (@alexanderlinne)
  • TUnit tests were updated to TUnit 0.90.45 and now run on Microsoft.Testing.Platform, in parallel with the VSTest-based test projects (mise run test). #491 (@mak638), #506 (@alexanderlinne)

New Contributors

Full Changelog: 0.13.4...0.13.5