A repository can add a bespoke check without a second architecture framework. Implement
IArchitectureRule and return the same ArchitectureViolation type as the built-in rules:
using Purview.Architecture.Testing;
[InheritsTests]
public sealed class ArchitectureTests : PurviewArchitectureTests
{
protected override IReadOnlyList<IArchitectureRule> AdditionalRules => [new NoConsoleLoggingRule()];
sealed class NoConsoleLoggingRule : IArchitectureRule
{
public string Rule => "noConsoleLogging";
public IReadOnlyList<ArchitectureViolation> Evaluate(ArchitectureContext context) =>
context.Projects.Values
.Where(project => !project.IsTestProject)
// ...inspect the project or its assembly...
.Select(project => new ArchitectureViolation(Rule, project.Name, "Console logging is banned here."))
.ToArray();
}
}ArchitectureContext carries the three inputs a rule can need:
| Member | What it is |
|---|---|
Policy |
The parsed ArchitecturePolicy, so a rule reads the same definition the built-in rules do. |
Projects |
The project graph, keyed by project name, with declared references, packages, framework references and analyzers. |
Assemblies |
The compiled assemblies, keyed by project name, each with the external types it references. Empty when assemblyRules.enabled is false. |
An additional rule participates in everything the built-in rules do:
- Its own
[Test]. Each rule id becomes a case ofAdditionalRule_GivenTheRepository_RespectsThePolicy, so a failure names the rule. SkipRule. OverrideSkipRuleand returntruefor the rule'sRuleid.- Waivers. A waiver whose
rulesnames the rule's id is honoured automatically, so a reviewed, per-project departure needs no code. - The same reporting.
ArchitecturePolicyEvaluator.Reportrenders the violations, and the failure's.Because(...)lists every one.
The evaluator takes an in-memory graph, so a rule is testable as a small fixture rather than a repository on disk:
var evaluator = new ArchitecturePolicyEvaluator(
policy,
projects,
assemblies,
[new NoConsoleLoggingRule()]
);
await Assert.That(evaluator.EvaluateAdditionalRule("noConsoleLogging")).IsEmpty();That is exactly how the package's own rules are tested.