IIMMPACT.CodeQuality
0.2.2
dotnet add package IIMMPACT.CodeQuality --version 0.2.2
NuGet\Install-Package IIMMPACT.CodeQuality -Version 0.2.2
<PackageReference Include="IIMMPACT.CodeQuality" Version="0.2.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="IIMMPACT.CodeQuality" Version="0.2.2" />
<PackageReference Include="IIMMPACT.CodeQuality"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add IIMMPACT.CodeQuality --version 0.2.2
#r "nuget: IIMMPACT.CodeQuality, 0.2.2"
#:package IIMMPACT.CodeQuality@0.2.2
#addin nuget:?package=IIMMPACT.CodeQuality&version=0.2.2
#tool nuget:?package=IIMMPACT.CodeQuality&version=0.2.2
iimmpact-dotnet-quality
IIMMPACT's shared C# code-quality rules: analyzer configuration shipped as a NuGet package, plus a dotnet tool that gates new violations on a per-declaration baseline committed to git.
Packages
| Package | What it is |
|---|---|
IIMMPACT.CodeQuality |
Analyzer config package. References SonarAnalyzer.CSharp and Microsoft.CodeAnalysis.BannedApiAnalyzers and injects a .globalconfig, SonarLint.xml, CodeMetricsConfig.txt, and BannedSymbols.IIMMPACT.txt into the build. DevelopmentDependency, so it never flows to downstream consumers of your package. |
IIMMPACT.CodeQuality.Tool |
dotnet tool iimmpact-quality with check, baseline and bootstrap commands. Builds the solution with SARIF logging enabled, collects diagnostics for the managed rules, attributes each to its owning declaration, and compares them against the code-quality-baseline.json committed at a trusted git revision. |
Managed rules
The package turns these rules on as warnings:
| Rule | Threshold | Source of the limit |
|---|---|---|
| CA1502 cyclomatic complexity | 25 | CodeMetricsConfig.txt |
| CA1506 class/method coupling | 60 (type), 40 (method) | CodeMetricsConfig.txt |
| S134 nesting depth | 4 | SonarLint.xml |
| S138 method lines | 60 | SonarLint.xml |
| S104 file lines | 400 | SonarLint.xml |
| S4462 blocking async calls | — | globalconfig |
| CA1849, CA2016 async correctness | — | globalconfig |
| CA2254 logging template | — | globalconfig |
| RS0030 banned APIs | DateTime.Now, DateTimeOffset.Now, System.Console, new HttpClient() |
BannedSymbols.IIMMPACT.txt |
| IDE0051, IDE0052, IDE0060 unused code | non-public parameters | globalconfig |
| S3776 cognitive complexity | 15 | SonarLint.xml |
CA2008, CA2012 task and ValueTask misuse |
— | globalconfig |
| CA2017, CA2241 logging and format argument mismatches | — | globalconfig |
| CA2000 undisposed objects | — | globalconfig |
| CA2013, CA2019, CA2022, CA2200, CA2208, CA2214 runtime correctness | — | globalconfig |
| CS8600, CS8601, CS8602, CS8603, CS8604, CS8618, CS8619, CS8625, CS8629 nullable correctness | — | globalconfig |
Requirements
- .NET SDK 9 or later.
CA2022ships with the .NET 9 SDK analyzers, so the tool rejects older SDKs with exit2instead of silently enforcing fewer rules. Projects may still target older frameworks such asnet8.0. <Nullable>enable</Nullable>in every checked project. The tool fails with exit2when nullable analysis does not run, or when handwritten source contains#nullable disable.
Usage
Reference the package in every project you want checked:
<PackageReference Include="IIMMPACT.CodeQuality" Version="0.2.2" PrivateAssets="all" />
Install the tool once per repo:
dotnet tool install IIMMPACT.CodeQuality.Tool
Propose a baseline from a trusted source revision, review and commit it, then gate:
dotnet iimmpact-quality bootstrap MyApp.slnx --source main # proposes code-quality-baseline.json
git add code-quality-baseline.json && git commit # human review approves it
dotnet iimmpact-quality check MyApp.slnx --base main # fails on anything over baseline
dotnet iimmpact-quality baseline MyApp.slnx --base main # reductions only: shrinks debt
check compares the working-tree code-quality-baseline.json and a fresh scan against the baseline committed at --base. The working-tree file may only remove allowances, lower ceilings or drop exclusions, so a branch cannot approve its own debt.
Exit codes:
0: every managed diagnostic is within the trusted baseline.1: gate failure. A violation has no allowance, a metric exceeds its ceiling, a frozen scope changed, the candidate baseline is not a reduction, or no baseline exists at--base.2: invalid input or analysis. The build failed, analysis was incomplete or altered, or the baseline JSON is malformed.
Check output
On failure, check prints one group per cause, in this order, each starting with a header <Title> (<count>): <hint> followed by its diagnostics:
| Group | Cause | Fix |
|---|---|---|
| Baseline not reduced | The working-tree code-quality-baseline.json adds or raises an allowance compared with the trusted base. |
Revert those entries. |
| New diagnostics | A managed diagnostic has no allowance. | Fix the code at each site. Never add allowances or suppressions. |
| Metrics above approved ceiling | A file, type or method measures above its approved ceiling. | Split it along a coherent responsibility. Do not raise the ceiling. |
| Metric rules without a measured value | A metric rule produced no measurement. | Tool problem: report it. |
| Frozen scopes changed | A declaration with recorded debt changed. | Make the declaration clean. baseline then drops the unused allowances; check passes without it. |
| Generated sources changed | A generated source was added or changed. | Ask the gate owner. A pull request cannot approve generated sources. |
Each diagnostic line keeps the form path(line,col): error RULE: message [declaration: reason], so GitHub problem matchers can annotate it. After the groups come Reproduce locally: dotnet iimmpact-quality check <solution as given> --base <full commit> and the last line, always FAIL: N violation(s) over baseline <12-character sha>. A pass prints a single PASS: line.
When GITHUB_STEP_SUMMARY is set and not empty, check also appends Markdown to that file: a one-line pass, or per group a heading, the hint and a Location | Rule | Declaration | Detail table of at most 50 rows (and N more, see the job log. follows when truncated), then the reproduce command. A failure to write the file prints a warning to stderr and never changes the exit code. Nothing is written when the variable is unset.
Every exit 2 message ends with a line saying the failure is a build, configuration or tool problem and not a code finding: fix the build error shown above, and if the build is clean, report the log to IIMMPACT-SDN-BHD/iimmpact-dotnet-quality.
check fails with exit 2 when:
- a project or target framework in the
ProjectReferenceclosure produced no fresh compiler evidence or SARIF log, - analyzers were skipped (
RunAnalyzersDuringBuild=false) or an analyzer failed (ADxxxx), - one of the analyzer families (NetAnalyzers including
CA2022, BannedApi, Sonar, code style, compiler nullable analysis) did not report its execution sentinel. An olderMicrosoft.CodeAnalysis.NetAnalyzerspackage that lacksCA2022fails this check, - the .NET SDK is older than 9, or handwritten source contains
#nullable disableor a#linedirective (which can move a diagnostic onto an approved declaration or hide it), - an analyzer or source generator is loaded from anywhere other than the .NET SDK or a package that restore resolved for the project (
project.assets.json, any package folder, symlinks resolved). A generator built in the repository, or a DLL copied into the NuGet folder, is rejected; add analyzers withPackageReference, not<Analyzer Include>paths. Every configured package folder is trusted, so keepRestorePackagesPathoutside the repository, - a
dotnet_code_qualityoption for a managed rule, or a generic one that applies to every rule, differs from the package's value, since options such asexcluded_symbol_namesnarrow what rules report. Options for unmanaged rules are left to the consumer. Category-scoped options such asdotnet_code_quality.Naming.api_surfaceare treated as generic and rejected, - the SARIF log is malformed, or a managed result is missing its location, message or metric,
- the effective configuration silences a managed rule (
.editorconfigor.globalconfigseverity in any casing,NoWarn, or a#pragma/[SuppressMessage]suppression). Suppressions from aDiagnosticSuppressorshipped in the SDK or a restored package, such as EF Core's forDbSetproperties, are honored, - the shipped
.globalconfig,SonarLint.xml,CodeMetricsConfig.txtorBannedSymbols.IIMMPACT.txtinput differs from the package content.
A source that Roslyn or SonarAnalyzer treats as generated (a file name that starts with TemporaryGeneratedFile_ or contains .g., _generated., .generated. or .designer.; an <auto-generated> or "generated by" header comment; an attribute ending in GeneratedCode, CompilerGenerated or DebuggerNonUserCode, including through a using alias in that file or a global using alias; or generated_code = true) must match an approved path and content hash in the baseline. Files the build writes into the tool's own fresh obj folder are exempt only when they have an <auto-generated> header and contain nothing but assembly attributes or global using lines (for example AssemblyInfo, GlobalUsings and ASP.NET's MvcApplicationPartsAssemblyInfo). Any other file there fails. Sources that a restored package adds to the compilation (for example Microsoft.NET.Test.Sdk's Program.cs) are trusted like its analyzers.
bootstrap only writes a proposal, and refuses to overwrite an existing file. It also refuses when any handwritten compile input, project file or Directory.Build.* file differs from the --source commit, or when the compile set differs from that commit. Files are compared with the raw committed blob, ignoring only CRLF/LF differences (checkout filters configured in the repository are not trusted). The only other permitted difference is a PackageReference to IIMMPACT.CodeQuality or IIMMPACT.CodeQuality.Tool. baseline writes only reductions. It refuses, and leaves the file unchanged, when any violation is not covered.
How the tool works
iimmpact-quality lists the solution's projects, evaluates each project's target frameworks and ProjectReference closure, then builds every compilation in dependency order into a fresh temporary artifacts directory with -p:IimmpactQualitySarif=true. Non-C# references such as F# projects are built but not analyzed. Scan builds turn off TreatWarningsAsErrors, because the gate reads the SARIF log and the injected sentinel warnings would otherwise fail the build. Each project's artifacts folder is named after a hash of its solution-relative path, so same-named projects in different folders do not overwrite each other. The package targets add an execution-sentinel source file to each compilation and write the actual csc command line, inputs and SARIF log to that invocation's evidence directory. The tool parses the command line with Roslyn's CSharpCommandLineParser and resolves effective analyzer options with AnalyzerConfigSet. Evidence from earlier builds is never read. Diagnostics for rules outside the managed list are ignored.
Allowances are per declaration (EntityKey = project path + TFM + source file + declaration identity), so a ceiling approved for one method cannot cover growth in another. MetricAllowance stores the measured ceiling for S138, S3776, CA1502 and CA1506. S104 is always attributed to the file. FrozenScopeAllowance stores a digest of the declaration's C# tokens and each diagnostic's token position for rules without a measurement (S134, RS0030, IDE*, and the rest). Comments and whitespace do not change either value, so they never invalidate an allowance. Any token change inside the declaration does. Declarations with the same name in one file (partial parts, or same-named local functions in sibling blocks) share one identity whose digest covers all of them: an allowance can never move between them, and editing or adding any one of them requires that scope to be clean.
Releasing
Tag v<version> where <version> equals the <Version> in Directory.Build.props. The release workflow verifies the tag, packs both packages, publishes to nuget.org via NuGet/login OIDC, and creates the GitHub release.
To retry an existing release without moving its tag, run:
gh workflow run release.yml --ref main -f tag=v0.1.0
Both triggers build from the tag, not the current branch. Publishing runs for the same tag are serialized, and packages already published are skipped.
License
MIT — IIMMPACT SDN BHD.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.BannedApiAnalyzers (>= 4.14.0)
- SonarAnalyzer.CSharp (>= 10.35.0.4138)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.