Digitals.Analyzers 0.10.2

dotnet add package Digitals.Analyzers --version 0.10.2
                    
NuGet\Install-Package Digitals.Analyzers -Version 0.10.2
                    
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="Digitals.Analyzers" Version="0.10.2">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Digitals.Analyzers" Version="0.10.2" />
                    
Directory.Packages.props
<PackageReference Include="Digitals.Analyzers">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
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 Digitals.Analyzers --version 0.10.2
                    
#r "nuget: Digitals.Analyzers, 0.10.2"
                    
#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 Digitals.Analyzers@0.10.2
                    
#: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=Digitals.Analyzers&version=0.10.2
                    
Install as a Cake Addin
#tool nuget:?package=Digitals.Analyzers&version=0.10.2
                    
Install as a Cake Tool

Digitals.Analyzers

One package that brings the Digitals C# analyzer set and the shared severity policy to a .NET repo. Reference it instead of wiring up four analyzer packages and copying an .editorconfig between repos.

This package covers production code. Test projects are covered by its companion, Digitals.Testing, which brings the test stack, the test severity policy and a coverage gate. Nothing test-specific lives here.

Install

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

Put it once in Directory.Build.props so it covers every project, and delete any existing references to the analyzer packages listed below — duplicates cause version conflicts.

The package is marked DevelopmentDependency, so PrivateAssets="all" is what NuGet applies by default anyway. It stops the analyzers flowing on to your consumers.

Set these alongside it, in the same Directory.Build.props:

<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors>nullable</WarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

The first two are enforced: the package fails the build with DIGB002 when Nullable is not enable and DIGB003 when TreatWarningsAsErrors is not true, because without them the whole severity policy is advisory. A project mid-migration can opt out explicitly, next to a comment saying why:

<DigitalsAllowNullableDisabled>true</DigitalsAllowNullableDisabled>
<DigitalsAllowWarningsNotAsErrors>true</DigitalsAllowWarningsNotAsErrors>

The NuGet vulnerability audit is checked too. Restore reports vulnerable packages as NU1901–NU1904, and TreatWarningsAsErrors turns those into a failed build, but only while the audit runs at full strength. DIGB004 is a warning, escalated to an error under TreatWarningsAsErrors, when NuGetAudit is not true, NuGetAuditMode is not all, NuGetAuditLevel is not low, or any NU1901–NU1904 is in NoWarn or WarningsNotAsErrors. The SDK defaults NuGetAuditMode to all only for net10.0 and later, so a project on an older target sets it in Directory.Build.props:

<NuGetAuditMode>all</NuGetAuditMode>

To accept one known advisory, suppress just that one with <NuGetAuditSuppress Include="https://github.com/advisories/GHSA-..." />. Weakening the audit as a whole takes <DigitalsAllowWeakNuGetAudit>true</DigitalsAllowWeakNuGetAudit>, next to a comment saying why.

WarningsAsErrors=nullable keeps nullable-flow warnings as errors even if warnings are later relaxed via WarningsNotAsErrors. Without EnforceCodeStyleInBuild the IDExxxx rules never fire in dotnet build — they stay IDE-only. GenerateDocumentationFile is what makes IDE0005 (unused usings) run in a build.

Migrating an existing repo, including how to stage the noise: docs/ADOPTION.md.

What you get

Analyzer package Rules
SonarAnalyzer.CSharp Quality and bug rules (S####)
Roslynator.Analyzers Refactoring and idiom rules (RCS####)
Roslynator.Formatting.Analyzers Formatting rules (RCS0###)
Microsoft.VisualStudio.Threading.Analyzers Async and threading rules (VSTHRD###)
Microsoft.CodeAnalysis.BannedApiAnalyzers Enforces the banned-API list (RS0030)

Plus configuration that is injected automatically — nothing to import:

  • Severity policy for the packs above, plus the naming rules, applied at global_level 100.
  • Banned APIs (RS0030) — the ambient clock: DateTime.Now, DateTime.UtcNow, DateTime.Today, DateTimeOffset.Now, DateTimeOffset.UtcNow. Inject TimeProvider instead: TimeProvider.System in your composition root, FakeTimeProvider in tests.
  • Banned APIs (RS0030) — the millisecond int overloads of Task.Delay, Thread.Sleep, new CancellationTokenSource(int) and CancelAfter. Delay(5) doesn't say its unit, so pass a TimeSpan: Task.Delay(TimeSpan.FromSeconds(5)).
  • Banned packages (DIGB001, a build error) — see the table below.
  • Custom rules (DT#####) — see the table below.
  • Sonar thresholds — a SonarLint.xml that raises the S1541 limit from Sonar's default of 10 to 15.

Some rules that ship disabled or info-only are escalated to warning: SYSLIB1045 (use [GeneratedRegex]), CA2254 (no interpolation in log message templates), VSTHRD100 (no async void), CA1707 (no underscores in identifiers), S6513 ([ExcludeFromCodeCoverage] must carry a Justification), CA1851 (an IEnumerable enumerated more than once), CA1310 (locale-dependent string comparison), CA1069 (duplicated enum values), S3776 (cognitive complexity over 15), S1541 (cyclomatic complexity over 15), S134 (control flow nested more than 3 deep), S104 (file over 1000 lines), CA5404/CA5405 (token validation disabled or always skipped), CA5390/CA5403 (hard-coded key or certificate), CA2248 (Enum.HasFlag with the wrong enum type), RCS1157 (a [Flags] composite covering an undefined bit), IDE0072/IDE0010 (a switch over an enum must name every member even when it has a default/_ arm — otherwise the discard silently swallows members added later; the compiler's own CS8509 only fires when there is no default arm). Others are set to suggestion so they show in the IDE without failing a build: CA2100 (raw SQL), CA1002/CA1819 (read-only collection properties), CA1021 (no out parameters), S109 (magic numbers), CA1515 (public types in an app project could be internal), RCS1079 (throws NotImplementedException), RCS1208 (an if wrapping the rest of a method body, invert it into a guard clause), S138 (method over 80 lines), S107 (more than 7 parameters), S1067 (more than 3 &&/|| in one expression).

Everything ships as warning, never error. Your repo decides how hard that bites via TreatWarningsAsErrors — which is why the package insists on it. The exceptions are the DIGB00x build errors: DIGB001 (banned package), DIGB002 (Nullable not enable), DIGB003 (TreatWarningsAsErrors not true). DIGB004 (NuGet audit weakened) is a warning like the rest, and fails the build through TreatWarningsAsErrors.

Custom rules

ID Rule Do this instead
DT00001 Any use of System.DateTime, including bare declarations like DateTime Foo { get; set; } DateOnly for a calendar date, TimeOnly for a time of day, TimeSpan for a duration, DateTimeOffset in UTC for an instant. DateTime is an interop-boundary exception
DT00002 DateTimeOffset.LocalDateTime / .DateTime — both quietly hand back a DateTime .UtcDateTime, the sanctioned boundary conversion
DT00003 new HttpClient() IHttpClientFactory via AddHttpClient — a client per call exhausts sockets
DT00004 dynamic Resolve the type at compile time: generics, DI, or a typed model
DT00006 double/float whose name looks like money (price, cost, fee, …). suggestion, IDE-only decimal
DT00007 A class deriving from System.TimeProvider FakeTimeProvider from Microsoft.Extensions.TimeProvider.Testing in tests, TimeProvider.System in production. Deriving from the real FakeTimeProvider to add helpers is fine
DT00008 A method whose return type is a nullable collection — IReadOnlyCollection<string>? GetOrders(), Task<List<Order>?>, T[]?, IAsyncEnumerable<T>? Return an empty collection: [], Array.Empty<T>(), ImmutableArray<T>.Empty. null and empty are the same answer to a caller, so the ? only buys a guard at every call site. string? is untouched, and so are properties and fields
DT00009 Any #region — named, unnamed, nested, or inside a method body Delete it. A region folds code out of sight, so a type grows past its job unnoticed and the standard member order stops being visible. If a file needs sections, it needs to be two files or two methods. Generated code is exempt automatically
DT00010 A bool or bool? field, property, parameter, local or bool-returning method whose name contains the word Not, No, Non, Cannot or Dont — IsNotEligible, _noConsent, cannotRenew Name the affirmative: IsEligible, HasConsent, CanRenew. Otherwise every caller writes !IsNotEligible. Words are matched whole, so IsNotified and HasNonce are fine. Overrides and interface implementations are reported on the declaration they satisfy, not again on each implementer
DT00011 A local that is read and then reassigned to a value that refers neither to it nor to anything computed from it — result = GetPatient(); Use(result); result = GetPrescription();. suggestion, IDE-only Declare a second local named for what it holds. Updates (total += x, s = s.Trim(), node = node.Next), loop reassignment, if (x is null) x = …, clearing to null, and assignments on separate branches are all fine
DT00012 A file whose name matches none of the top-level types it declares — class Patient in Patients.cs Rename the file to Patient.cs. The name is read up to the first ., { or `, so Patient.Validation.cs (partial class), Result{T}.cs and Result`1.cs all pass. A record sharing its file with its validator passes if either name matches. Files with no type, files with top-level statements, and file-scoped types are skipped

These carry their own IDs rather than reusing RS0030 on purpose: a #pragma disabling an interop DateTime at a boundary must not also switch off the ambient-clock ban in the same region.

Banned packages

A direct PackageReference to any of these fails the build with DIGB001. Transitive dependencies are not checked. Test frameworks, assertion and mocking libraries are banned by Digitals.Testing instead, under DIGT002.

Instead of Use
Newtonsoft.Json, Microsoft.AspNetCore.Mvc.NewtonsoftJson System.Text.Json
AutoMapper and its DI extension Explicit mapping code (v15+ needs a commercial licence)
EntityFramework Microsoft.EntityFrameworkCore
System.Data.SqlClient Microsoft.Data.SqlClient
Microsoft.EntityFrameworkCore.Proxies Explicit loading — no lazy loading

If you genuinely need one, allow it explicitly with a comment saying why:

<DigitalsAllowBannedPackages>Newtonsoft.Json</DigitalsAllowBannedPackages>

Turning something off

A whole rule, repo-wide. A repo-local .editorconfig always beats the packaged policy. This is the supported opt-out, not a workaround:

[*.cs]
dotnet_diagnostic.S1234.severity = none

A Sonar threshold. Sonar reads rule parameters only from an AdditionalFiles item named exactly SonarLint.xml, never from .editorconfig. Add your own and the packaged one steps aside:

<ItemGroup>
  <AdditionalFiles Include="$(MSBuildThisFileDirectory)SonarLint.xml" />
</ItemGroup>

Your file replaces the packaged thresholds as a whole, so copy across any rule you still want at the org value. The packaged file is in the package under build/config/SonarLint.xml.

One line. Legitimate exceptions exist — bootstrap logging before DI is built, EF migration defaults:

#pragma warning disable RS0030 // Serilog bootstrap, DI not built yet
Log.Information("Starting at {Now}", DateTimeOffset.UtcNow);
#pragma warning restore RS0030

Suppressions are greppable and show up in review. If they multiply in one area, that is worth a conversation rather than more pragmas.

What your repo still needs

A global config can only carry severities, so editor behaviour stays in your own .editorconfig:

root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true

[*.{props,csproj}]
indent_style = tab
indent_size = 4
tab_width = 4

[*.{cs,vb,cshtml}]
indent_style = tab
indent_size = 4
tab_width = 4
max_line_length = 120

Any path-scoped exceptions live there too ([**/Pages/**.cshtml.cs], [**/Migrations/**.cs]) — a global config has no path sections.

EF Core migrations are not marked as generated code, so analyzers treat them as hand-written. The package exempts files under any Migrations/ folder from S104 (file over 1000 lines) on its own. If you moved migrations elsewhere with dotnet ef migrations add -o, exempt that folder yourself:

[**/Data/Schema/**.cs]
dotnet_diagnostic.S104.severity = none

Seeing suggestions outside the IDE

dotnet build prints warnings and errors only. Suggestion-level diagnostics (S138, S107, S1067, S109, ...) never reach the build output, at any verbosity. To get them in a file, which is how a coding agent or a script can read them, pass DigitalsSarifDir:

dotnet build -p:DigitalsSarifDir=artifacts/sarif

Each project writes <Project>.<tfm>.sarif there, in SARIF 2.1 (JSON). A suggestion is a result with "level": "note". A relative path resolves against the directory you run dotnet build from. Filter by rule ID: AnalysisMode=Recommended adds hundreds of notes of its own. A log that is missing is regenerated on the next build, even an up-to-date one. If you set ErrorLog yourself, yours wins.

Notes

C# only in practice. Sonar C# and Roslynator are C#-only; the threading and banned-API analyzers also cover VB. In an F# project the package does nothing.

One version to review. Bumping any bundled analyzer means a new version of this package, so an upgrade is a single version bump and a single changelog to read across every repo.

Versions come from git tags via MinVer and follow SemVer. Release notes: Releases.

License

MIT — see LICENSE. This covers the code in this repository: the config files, the banned-package check and the custom DT##### analyzer. The bundled third-party analyzers keep their own licences and reach you as ordinary NuGet dependencies.

Working on the package itself: docs/MAINTAINING.md.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

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.10.2 0 9/29/2026
0.10.1 181 9/23/2026
0.10.0 67 9/23/2026
0.9.0 83 9/23/2026
0.8.0 130 9/21/2026
0.7.2 105 9/19/2026
0.7.1 88 9/19/2026
0.7.0 84 9/19/2026
0.6.0 462 8/31/2026
0.5.1 257 8/26/2026
0.5.0 147 8/20/2026