HostLoom.Analyzers 0.16.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package HostLoom.Analyzers --version 0.16.0
                    
NuGet\Install-Package HostLoom.Analyzers -Version 0.16.0
                    
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="HostLoom.Analyzers" Version="0.16.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HostLoom.Analyzers" Version="0.16.0" />
                    
Directory.Packages.props
<PackageReference Include="HostLoom.Analyzers" />
                    
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 HostLoom.Analyzers --version 0.16.0
                    
#r "nuget: HostLoom.Analyzers, 0.16.0"
                    
#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 HostLoom.Analyzers@0.16.0
                    
#: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=HostLoom.Analyzers&version=0.16.0
                    
Install as a Cake Addin
#tool nuget:?package=HostLoom.Analyzers&version=0.16.0
                    
Install as a Cake Tool

HostLoom.Analyzers

Roslyn analyzers for correct asynchronous and dependency-injection usage of HostLoom. The package contains compiler analyzers only: it adds no runtime assembly or dependency to the application.

dotnet add package HostLoom.Analyzers

For central package management:

<PackageReference Include="HostLoom.Analyzers" PrivateAssets="all" />

Rules

HLM0001

Pass an available cancellation token to HostLoom asynchronous calls. The analyzer recognizes both a CancellationToken parameter and an IPipeContext.CancellationToken available from an enclosing pipeline filter or helper. The cache and lock contracts (ICache, IDistributedLock, ILockHandle) are recognized by name, so the rule covers them whether the call goes through the real package or a test stub of the interface.

await client.GetResponseAsync(
    "orders",
    request,
    cancellationToken: cancellationToken
);

HLM0002

Await HostLoom asynchronous operations instead of blocking through .Result, .Wait(), or .GetAwaiter().GetResult(). The rule also recognizes .AsTask() over a HostLoom ValueTask, and covers the cache and lock contracts, whose members all return ValueTask.

HLM0003

Register implementations of IRequestHandler<,>, IEventHandler<>, and IRequestBehavior<,> as scoped services. HostLoom creates a dependency-injection scope per delivery; a singleton handler or behavior can share mutable state between deliveries or capture scoped dependencies. AddHandler, AddSubscriber, and AddBehavior already apply the correct scoped lifetime.

HLM0004

Assign every settable member of a mapped destination. An explicit map makes a forgotten member a silent data loss rather than a compile error — nothing requires the member to be written — which is the one axis on which explicit mapping is less safe than the convention mapping it replaces. A member supplied through the destination's constructor counts as assigned, so positional records and contracts with real constructors need nothing extra. Inherited members are included, and an overridden property is counted once. Interface destinations include members inherited from other interfaces; assignments to concrete implementation properties satisfy the corresponding interface members.

Omitting a member is often correct. Name each one, so that a member added to the contract later is still reported rather than excused by a blanket marker:

[UnmappedMembers(nameof(ProductModel.ImageUrl), nameof(ProductModel.VendorId))]
public sealed class ProductMapper : IMapper<ProductEntity, ProductModel>

HLM0005

Keep a Map body in a shape completeness can be verified in. Two are recognised:

  • the destination constructed and returned directly, with an object initializer;
  • one local constructed, assigned into across any number of statements and branches, and returned.

A body outside both — the local passed to a method, different locals returned on different paths, or a destination returned from somewhere else — is reported rather than skipped, so that "not checked" is never mistaken for "checked and complete". Conditional assignment counts as assigned: the rule targets forgotten members, and evidence that the author considered one is the whole signal.

Lambda and local-function bodies do not contribute returns or assignments to the enclosing map. A lambda used to compute a member value does not change the recognized map shape. A nested function that captures the destination local produces HLM0005: its effects cannot be verified without following when and how the function executes. A concrete local may be returned through a base-class or interface destination contract.

A map whose destination is a type parameter — a generic map class closed through MappingBuilder.Add<TSource, TDestination>(factory) — cannot have its members enumerated, so neither rule applies to it; HLM0017 reports that instead of staying silent.

Neither rule inspects IUpdateMapper<TSource, TDestination>.MapInto. An update map writes into an instance the caller already holds, and a partial update is its normal case: every member it does not assign keeps its value by design, so an unassigned member is not the data loss HLM0004 exists to catch. State the members an update map leaves alone in its documentation and pin them with a test. A class that both creates and updates one pair is checked on its Map body only.

HLM0017

Know which maps completeness does not cover. A generic map whose destination is a type parameter has no members to enumerate until it is closed, so HLM0004 cannot check it and a forgotten assignment in it ships silently. This informational diagnostic marks the map so the omission is visible in the build output and in the editor, and its message names the map and the parameter. Cover each closed pair of such a map with a test that asserts every destination member, or close the pair in a concrete map class where HLM0004 applies. A map generic only in its source is still checked, because its destination is concrete.

HLM0006

Do not capture the scoped IMapper dispatcher in a singleton. AddHostLoomMapping registers the non-generic dispatcher scoped, so a singleton or an IHostedService that takes one is a captive dependency — and the failure is asymmetric: it throws at host build where scope validation is enabled, the generic host's default in Development, and succeeds where it is not. Left to run time it is a Development-only failure that Production would not reproduce.

Inject the closed IMapper<TSource, TDestination> instead, which is transient and carries no such restriction, or take IServiceScopeFactory and resolve per unit of work. The rule reports only the non-generic dispatcher; the closed map it points at is never reported.

HLM0007

Hash a secret before it becomes part of a cache or lock key. A key is written to the backend, appears in logs, and is tagged on spans, so a key built from a token, secret, password, or API key copies the credential into all three. The rule reports an interpolation hole, a concatenation operand, or a string.Format, string.Concat, or string.Join argument of the key (or keys) argument of an ICache or IDistributedLock member that references a parameter, local, field, or property whose name contains token, secret, password, refreshToken, or apiKey. Wrap the value in CacheKey.FromSensitive or LockKey.FromSensitive, which hashes it so the key stays unique without carrying the secret:

await cache.GetOrCreateAsync(
    $"session:{CacheKey.FromSensitive(refreshToken)}",
    token => LoadSessionAsync(token),
    TimeSpan.FromMinutes(5),
    cancellationToken
);

The one-argument form is a plain hash and suits only high-entropy inputs such as tokens and session ids. A password, PIN, or other low-entropy secret needs the keyed overload, FromSensitive(value, key), whose HMAC cannot be brute-forced from a leaked key namespace.

A key that is entirely a FromSensitive call is never reported. A CancellationToken operand is ignored even though its name contains token.

HLM0008

Forward the cancellation token a get-or-create factory receives. GetOrCreateAsync hands the caller's token to the factory so the work it starts stops with the request; a factory that names the token and never uses it keeps running after the caller has gone, holding the per-key guard while it does. The rule reports a lambda, anonymous method, or method group whose CancellationToken parameter is never referenced in its body, including from nested lambdas. Name the parameter _ to state that the factory has nothing to cancel.

await cache.GetOrCreateAsync("catalog", token => LoadCatalogAsync(token), options, cancellationToken);
await cache.GetOrCreateAsync("constant", _ => ValueTask.FromResult(42), options, cancellationToken);

Configuration

All rules except the informational HLM0017 are warnings by default and use the HLM diagnostic prefix. Standard .editorconfig configuration can change a rule's severity:

dotnet_diagnostic.HLM0001.severity = error

Generated code is ignored, and analyzer execution is safe for concurrent compiler execution.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.20.0 226 10/31/2026
0.19.0 109 10/29/2026
0.18.0 135 10/27/2026
0.17.0 149 10/25/2026
0.16.1 134 10/22/2026
0.16.0 178 10/14/2026
0.15.1 128 10/12/2026
0.15.0 183 10/4/2026
0.14.0 106 10/4/2026
0.13.0 138 9/28/2026
0.12.0 141 9/27/2026
0.11.0 138 9/25/2026
0.10.0 131 9/22/2026
0.9.0 113 9/22/2026
0.8.0 145 9/19/2026
0.7.0 125 9/17/2026
0.6.0 126 9/6/2026
0.5.0 120 9/5/2026
0.4.0 120 9/3/2026
0.3.0 130 8/28/2026
Loading failed