Siemens.AspNet.CodeRules.Engine 7.7.4

Prefix Reserved
This package has a SemVer 2.0.0 package version: 7.7.4+3.
dotnet add package Siemens.AspNet.CodeRules.Engine --version 7.7.4
                    
NuGet\Install-Package Siemens.AspNet.CodeRules.Engine -Version 7.7.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Siemens.AspNet.CodeRules.Engine" Version="7.7.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Siemens.AspNet.CodeRules.Engine" Version="7.7.4" />
                    
Directory.Packages.props
<PackageReference Include="Siemens.AspNet.CodeRules.Engine" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Siemens.AspNet.CodeRules.Engine --version 7.7.4
                    
#r "nuget: Siemens.AspNet.CodeRules.Engine, 7.7.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Siemens.AspNet.CodeRules.Engine@7.7.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Siemens.AspNet.CodeRules.Engine&version=7.7.4
                    
Install as a Cake Addin
#tool nuget:?package=Siemens.AspNet.CodeRules.Engine&version=7.7.4
                    
Install as a Cake Tool

Siemens.AspNet.CodeRules.Engine

Parse the solution once. Run every rule against it. Report findings a human and an AI agent can act on.

The code rule engine behind all hosts. It registers your rules with the usual IServiceCollection API, creates every analysis context once (the solution is parsed a single time, also across several projects), runs the rules in parallel and returns results with findings, skips and errors. It also contains ICodeRuleSetup, the composition root of a code rule test project, and RuleTester<TRule>, the unit test helper for single rules.

Most consumers never reference it directly: the test adapters (Siemens.AspNet.CodeRules.MSTest, .XUnit, .NUnit) and the coderules CLI bring it. Reference it yourself when you write rule tests or your own host.

services.AddCodeRuleEngine("My.slnx", o => o.AddRulePack<NamingRulePack>()
                                           .AddRulesFrom(typeof(MyLocalRule).Assembly)
                                           .UseDocumentationUrlTemplate("https://example.com/rules#{Id}"));

Installation

dotnet add package Siemens.AspNet.CodeRules.Engine

Rules themselves only need Siemens.AspNet.CodeRules.Contracts.


ICodeRuleSetup: one setup for every host

A code rule test project has no Program.cs, so it has exactly one class that implements ICodeRuleSetup:

public sealed class CodeRuleSetup : ICodeRuleSetup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCodeRuleEngine("My.slnx", o => o.AddRulePack<NamingRulePack>());
    }
}

It is the single source for all hosts:

  • the source generators of the test adapters read it at build time and write one test per registered rule,
  • the test adapters call it at test time and run the rule of each test,
  • the CLI (coderules run) loads it from the built project and runs it natively.

So dotnet test and coderules always run the same rules with the same result.


Registering rules

AddCodeRuleEngine(solution, configure) registers the rules, the catalog, the context cache and ICodeRuleEngine as singletons. solution is a file name or path (e.g. My.slnx), searched from the running assembly upwards.

Option Does
AddRule<TRule>() adds one rule explicitly, without reflection
AddRules(r => r.Add<TRule>()) adds several rules explicitly, through the same ICodeRuleRegistry a pack uses
AddRulePack<TPack>() adds the rules an ICodeRulePack contributes
AddRulesFrom(assembly) adds every public, non-abstract rule class of an assembly
SkipRule<TRule>(reason) registers a rule but reports it as skipped, e.g. a rule from a pack you do not own: "Replaced by analyzer X"
SkipRulePack<TPack>(reason) registers a pack's rules but reports them as skipped; meant for implicitly loaded packs, an explicit AddRulePack can simply be left out
SkipGroup(group, reason) / SkipTag(tag, reason) reports every registered rule of a group / with a tag as skipped; an unknown group or tag is a configuration error. Reason precedence: rule > pack > group > tag > IRule.SkipReason
SkipRule<TRule>(reason, scope => scope.InProject("X").InNamespace("A.B.*").InPath("src/Legacy/**")) (also SkipRulePack, SkipGroup, SkipTag) the rule runs, only its findings in project X, namespace A.B (and below) or below src/Legacy are dropped (conditions are ORed) and reported as "N skipped in InProject("X")"; an unscoped skip wins over every scope. See Core contract §6.0.2
ExcludeFromAnalysis(reason, scope => scope.InPath("src/Eval")) takes C# files out of the CodeContext: no rule sees them (experiments, generated code). Reported once per run with the number of files. See Core contract §6.0.3
UseDocumentationUrlTemplate(template) links every rule without its own DocumentationUrl; {Id} is replaced by the rule ID
UseOutputMode(CodeRuleOutputMode.Ai) failure text of the test adapters for this project: Human (default) or Ai, see Output mode
AddContextProvider<TContext, TProvider>() a provider for an own context that cannot simply be built from other contexts

All options combine, in any order; registering the same rule twice is harmless. The generators understand AddRule<TRule>(), Add<TRule>(), AddRulePack<TPack>() and AddRulesFrom(typeof(X).Assembly).

Own contexts with one public constructor that takes other contexts (e.g. ControllerContext(CodeContext code)) need no registration: the engine creates them once per run.


Accepting a single finding in the code

A skip in the setup is for whole rules, packs, groups, tags or scopes. A single finding is accepted where it is, the same way as an analyzer finding, always with a reason:

[SuppressMessage("CodeRules", "NAMING_001", Justification = "Name is part of the public contract of v1")]
public sealed class LegacyOrderManager { /* ... */ }
#pragma warning disable NAMING_001 // Generated by the legacy importer, removed with #1234
public sealed class ImportManager { /* ... */ }
#pragma warning restore NAMING_001
  • [SuppressMessage] counts on every declaration that contains the finding line (member, accessor, parameter, type, outer type). The category is free, the check ID is the rule ID ("NAMING_001:Title" works too).
  • #pragma warning disable <ID> counts from the next line up to the matching restore (or the end of the file). A pragma without an ID is ignored.
  • Without a reason it does not count: no Justification, or no // comment after the pragma, keeps the finding and says why. The same happens for rules with AllowSuppression => false.
  • Accepted findings do not fail the rule; they are listed as Suppressed in the result and in both outputs, so they stay visible. Only findings with a line in a C# file can be accepted this way.

Output mode: human or AI

Every host can write its output for people (Human, the default) or as JSON for AI agents (Ai, the same schema in every host). How you switch depends on the host:

Host Switch
Test adapters (MSTest, xUnit, NUnit) in the setup with UseOutputMode(...) and/or the environment variable
coderules CLI --format ai (setup and environment are ignored)
RuleTester<TRule> (rule unit tests) new RuleTester<TRule>(o => o.OutputMode = CodeRuleOutputMode.Ai), otherwise the environment variable

In the setup: the default of the project

public sealed class CodeRuleSetup : ICodeRuleSetup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCodeRuleEngine("My.slnx", o => o.AddRulePack<NamingRulePack>()
                                                   .UseOutputMode(CodeRuleOutputMode.Ai));
    }
}

Good when the project is mostly run by an AI agent: every dotnet test writes the JSON, without any setup on the machine.

With an environment variable: per machine, CI or agent

$env:CodeRuleSettings__OutputMode = "ai"     # PowerShell
export CodeRuleSettings__OutputMode=ai       # bash

TestSdkSettings__OutputMode=ai (the variable of AspNetCore.Simple.MsTest.Sdk) also works and switches API tests and code rules together. Without a shell, put the variable in a .runsettings file of the test project:

<RunSettings>
  <RunConfiguration>
    <EnvironmentVariables>
      <CodeRuleSettings__OutputMode>ai</CodeRuleSettings__OutputMode>
    </EnvironmentVariables>
  </RunConfiguration>
</RunSettings>

Which one wins

  1. CodeRuleSettings__OutputMode, if set to ai or human,
  2. otherwise TestSdkSettings__OutputMode, if set to ai or human,
  3. otherwise UseOutputMode(...) of the setup,
  4. otherwise Human.

So the environment always overrides the setup: a project with UseOutputMode(CodeRuleOutputMode.Ai) still shows the human output with CodeRuleSettings__OutputMode=human. Other values, such as the SDK's hybrid, leave the decision to the setup. The mode is resolved once per test run, after the setup ran, so Environment.SetEnvironmentVariable inside ConfigureServices has no effect: use UseOutputMode(...) there.

The CLI has its own --format option because one call can run several code rule projects with different setups.


Your own host

var services = new ServiceCollection();
services.AddCodeRuleEngine("My.slnx", o => o.AddRulePack<NamingRulePack>());

await using var provider = services.BuildServiceProvider();
var engine = provider.GetRequiredService<ICodeRuleEngine>();

var run = await engine.RunAllAsync(cancellationToken);                        // every rule
var naming = await engine.RunAsync(RuleFilter.Tag("Naming"), cancellationToken); // a selection
var single = await engine.RunAsync("NAMING_001", cancellationToken);           // exactly one rule

run.EnsureSuccess();   // throws with a readable report when a rule failed or could not run
Type Content
RunResult one RuleResult per rule, Overall outcome, IsSuccess, EnsureSuccess()
RuleResult the rule descriptor, Outcome (Passed, Failed, Skipped, Error), findings, Suppressed (in code), SkippedInScope (scoped skips), FindingProjects, FindingNamespaces, EnsureSuccess()
RuleFilter All, Id("NAMING_*"), Group("Controllers"), Tag("Naming"), Of<TRule>()
ICodeRuleEngine.Catalog the validated rules (IDs, groups, tags) without running anything

Output

  • RuleResultFormatter.Format(results): the human report.
  • AiResultFormatter.Format(results, rerun): the JSON schema shared by all hosts (test adapters and CLI), including the instructions for AI agents ("fix the code, not the rule") and, with the optional rerun callback, the command that checks a fix per result.
  • RepositoryPath: repository relative, clickable path:line:column locations.
  • The test adapters switch with UseOutputMode(...) in the setup or CodeRuleSettings__OutputMode=ai, see Output mode; CodeRuleOutput.Resolve(configured) applies the same order in your own host.

Testing a rule: RuleTester<TRule>

Every rule deserves its own unit tests. RuleTester<TRule> (namespace Siemens.AspNet.CodeRules.Engine) runs one rule against inline code. No solution is parsed and every test gets its own engine, so the tests run in parallel. It works with any test framework: a failure is a RuleTestException with a readable message.

[TestClass]
public sealed class RecordPropertiesMustBeImmutable_Tests(TestContext testContext)
{
    private readonly RuleTester<RecordPropertiesMustBeImmutable> _rule = new();

    [TestMethod]
    public async Task Should_Report_Finding_When_Record_Property_Has_A_Setter()
    {
        // The rule must report exactly the code marked with [| ... |].
        await _rule.ViolatesAsync("""
                                  public record Order(int Id)
                                  {
                                      [|public string Status { get; set; } = "";|]
                                  }
                                  """, testContext.CancellationToken);
    }

    [TestMethod]
    public async Task Should_Report_No_Finding_When_Record_Property_Is_Immutable()
    {
        await _rule.CompliesAsync("""
                                  public record Order(int Id)
                                  {
                                      public string Status { get; init; } = "";
                                  }
                                  """, testContext.CancellationToken);
    }

    [TestMethod]
    public async Task Should_Suggest_An_Init_Accessor()
    {
        var finding = await _rule.ViolatesOnceAsync("""
                                                    public record Order(int Id)
                                                    {
                                                        [|public string Status { get; set; } = "";|]
                                                    }
                                                    """, testContext.CancellationToken);

        Assert.AreEqual("public string Status { get; init; } = \"\";", finding.ShouldBe);
    }
}
Method Checks
ViolatesAsync the rule reports exactly the marked spans ([|...|], several allowed)
CompliesAsync the rule reports nothing
ViolatesOnceAsync exactly one finding, returned to check What, ShouldBe, Details, ...
RunAsync the raw RuleResult

Every method also takes several files: TestFile.Production("Order.cs", code) and TestFile.Test("OrderTests.cs", code) fill ProductionTrees and TestTrees. new RuleTester<TRule>(o => ...) adds own contexts (AddContext, AddContextProvider) or services (ConfigureServices).

License

Copyright (c) Siemens AG. See LICENSE.md in the package.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Siemens.AspNet.CodeRules.Engine:

Package Downloads
Siemens.AspNet.CodeRules.MSTest

Runs code rules as MSTest tests: one ICodeRuleSetup in your test project, and a source generator writes one test per registered rule at build time. Test Explorer, categories per group and tag, human or AI output.

Siemens.AspNet.CodeRules.XUnit

Runs code rules as xUnit v3 tests: one ICodeRuleSetup in your test project, and a source generator writes one test per registered rule at build time. Test Explorer, category traits per group and tag, human or AI output.

Siemens.AspNet.CodeRules.NUnit

Runs code rules as NUnit 4 tests: one ICodeRuleSetup in your test project, and a source generator writes one test per registered rule at build time. Test Explorer, categories per group and tag, human or AI output.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
7.7.4 34 11/14/2026
7.7.3 41 11/13/2026
7.7.2 46 11/13/2026
7.7.1 37 11/12/2026
7.7.0 45 11/12/2026

## Next

First release. The engine behind the test adapters and the `coderules` CLI.

- `AddCodeRuleEngine(solution, options)`: register rules with `AddRule`, `AddRules`, `AddRulePack` and `AddRulesFrom`; the solution is parsed once per run, contexts are shared and rules run in parallel.
- Skips with a reason for a rule, pack, group or tag, optionally limited to a scope (`InProject`, `InNamespace`, `InPath`); `ExcludeFromAnalysis` takes files out of the analysis (experiments, generated code).
- Accept single findings in the code with `[SuppressMessage]` or `#pragma warning disable`, always with a reason; rules can forbid it with `AllowSuppression => false`.
- `ICodeRuleSetup`: one composition root per code rule project, read by the source generators, the test adapters and the CLI alike.
- `ICodeRuleEngine` for own hosts: `RunAllAsync`, `RunAsync` with `RuleFilter`, the rule catalog and `EnsureSuccess`.
- Human output and one JSON schema for AI agents (with a `rerun` command per result), switched with `UseOutputMode` or `CodeRuleSettings__OutputMode`.
- `RuleTester<TRule>` for unit tests of single rules against inline code, independent of the test framework.