Digitals.Analyzers
0.7.2
See the version list below for details.
dotnet add package Digitals.Analyzers --version 0.7.2
NuGet\Install-Package Digitals.Analyzers -Version 0.7.2
<PackageReference Include="Digitals.Analyzers" Version="0.7.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Digitals.Analyzers" Version="0.7.2" />
<PackageReference Include="Digitals.Analyzers"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Digitals.Analyzers --version 0.7.2
#r "nuget: Digitals.Analyzers, 0.7.2"
#:package Digitals.Analyzers@0.7.2
#addin nuget:?package=Digitals.Analyzers&version=0.7.2
#tool nuget:?package=Digitals.Analyzers&version=0.7.2
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>
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. InjectTimeProviderinstead:TimeProvider.Systemin your composition root,FakeTimeProviderin tests. - Banned packages (
DIGB001, a build error) — see the table below. - Custom rules (
DT#####) — see the table below.
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), 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).
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).
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 |
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
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.
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.
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.BannedApiAnalyzers (>= 3.3.4)
- Microsoft.VisualStudio.Threading.Analyzers (>= 18.7.23)
- Roslynator.Analyzers (>= 5.0.0)
- Roslynator.Formatting.Analyzers (>= 5.0.0)
- SonarAnalyzer.CSharp (>= 10.33.0.1635)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.