HostLoom.Analyzers
0.12.0
See the version list below for details.
dotnet add package HostLoom.Analyzers --version 0.12.0
NuGet\Install-Package HostLoom.Analyzers -Version 0.12.0
<PackageReference Include="HostLoom.Analyzers" Version="0.12.0" />
<PackageVersion Include="HostLoom.Analyzers" Version="0.12.0" />
<PackageReference Include="HostLoom.Analyzers" />
paket add HostLoom.Analyzers --version 0.12.0
#r "nuget: HostLoom.Analyzers, 0.12.0"
#:package HostLoom.Analyzers@0.12.0
#addin nuget:?package=HostLoom.Analyzers&version=0.12.0
#tool nuget:?package=HostLoom.Analyzers&version=0.12.0
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 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.
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 |