diff --git a/.editorconfig b/.editorconfig index 850846d0b..c6c807297 100644 --- a/.editorconfig +++ b/.editorconfig @@ -106,13 +106,17 @@ ij_json_wrap_long_lines = false dotnet_diagnostic.ca1848.severity = none # --- Static-analysis pack adoption (ersatztv#15) --- -# Roslynator / SonarAnalyzer / Meziantou / AsyncFixer are referenced centrally -# (Directory.Build.targets). Default every analyzer diagnostic to `suggestion` so the new -# packs don't fail the TreatWarningsAsErrors build; high-value rules get promoted to -# warning/error one at a time (see ersatztv#15 / docs/contributing.md). Explicit per-rule -# severities (e.g. ca1848 above) still take precedence over this bulk default. +# Threading analyzers and Roslynator / SonarAnalyzer / Meziantou / AsyncFixer are enabled centrally. +# Default their diagnostics to `suggestion`; the SDK's exact per-rule suggestion baseline lives in +# eng/analyzers/sdk-all-suggestion.globalconfig because AnalysisLevel=latest-All otherwise injects +# exact warning severities that outrank this bulk setting. High-value rules are promoted one at a +# time. Explicit per-rule severities (e.g. ca1848 above) take precedence over both baselines. dotnet_analyzer_diagnostic.severity = suggestion +# A collection count can never be negative. Treat comparisons that therefore collapse to a +# constant as errors; the first promotion caught a busy/idle branch that was permanently busy. +dotnet_diagnostic.S3981.severity = warning + # Blazor components: analyzers run on .razor/.cshtml @code too, and TWAE would otherwise # turn their default-severity findings into build errors — keep them at suggestion as well. [*.razor] diff --git a/Directory.Build.props b/Directory.Build.props index 74f7120cb..d65409840 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,6 +3,12 @@ develop false true + + true + latest-All + true + TreatWarningsAsErrors), so "criticals block" actually holds repo-wide. + S3981 is the first explicitly promoted analyzer rule (ersatztv#15). --> $(WarningsNotAsErrors);NU1901;NU1902;NU1903 - $(WarningsAsErrors);NU1904 + $(WarningsAsErrors);NU1904;S3981 + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets index 8703b853f..030a436ef 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,9 +1,7 @@ - - false - - - + + @@ -12,11 +10,11 @@ - + all diff --git a/ErsatzTV.Application/ErsatzTV.Application.csproj b/ErsatzTV.Application/ErsatzTV.Application.csproj index 73d35a499..4625ba925 100644 --- a/ErsatzTV.Application/ErsatzTV.Application.csproj +++ b/ErsatzTV.Application/ErsatzTV.Application.csproj @@ -4,7 +4,6 @@ net10.0 VSTHRD200,CA1873 enable - latest-Recommended true Debug;Release;Debug No Sync diff --git a/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs b/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs index 279b056e6..bbdf6cb2d 100644 --- a/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs +++ b/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs @@ -27,7 +27,7 @@ public class ReleaseMemoryHandler : IRequestHandler return Task.CompletedTask; } - bool hasActiveWorkers = _ffmpegSegmenterService.Workers.Count >= 0 || FFmpegProcess.ProcessCount > 0; + bool hasActiveWorkers = _ffmpegSegmenterService.Workers.Count > 0 || FFmpegProcess.ProcessCount > 0; if (request.ForceAggressive || !hasActiveWorkers) { _logger.LogDebug("Starting aggressive garbage collection"); diff --git a/ErsatzTV.Core/ErsatzTV.Core.csproj b/ErsatzTV.Core/ErsatzTV.Core.csproj index d89b45f03..b492f378d 100644 --- a/ErsatzTV.Core/ErsatzTV.Core.csproj +++ b/ErsatzTV.Core/ErsatzTV.Core.csproj @@ -4,7 +4,6 @@ net10.0 VSTHRD200,CA1873 enable - latest-Recommended true disable diff --git a/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj b/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj index ae249a3d3..5b88292b9 100644 --- a/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj +++ b/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj @@ -4,7 +4,6 @@ net10.0 enable enable - latest-Recommended true true VSTHRD200,CA1873 diff --git a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj index 1613bf6fd..9608cf1cc 100644 --- a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj +++ b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj @@ -5,7 +5,6 @@ true VSTHRD200,CA1873 enable - latest-Recommended true Linux true diff --git a/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj b/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj index 677e3e8d8..b314b7327 100644 --- a/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj +++ b/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj @@ -8,7 +8,6 @@ Debug;Release;Debug No Sync AnyCPU 729e6271-c307-43c8-8e36-1b36c39f6de2 - latest-Recommended true VSTHRD200,CA1873 diff --git a/ErsatzTV.Tests/Application/Maintenance/ReleaseMemoryHandlerTests.cs b/ErsatzTV.Tests/Application/Maintenance/ReleaseMemoryHandlerTests.cs new file mode 100644 index 000000000..28d6fb363 --- /dev/null +++ b/ErsatzTV.Tests/Application/Maintenance/ReleaseMemoryHandlerTests.cs @@ -0,0 +1,49 @@ +using ErsatzTV.Application.Maintenance; +using ErsatzTV.Core.FFmpeg; +using ErsatzTV.Core.Interfaces.FFmpeg; +using Microsoft.Extensions.Logging; +using NSubstitute; +using NUnit.Framework; +using Shouldly; + +namespace ErsatzTV.Tests.Application.Maintenance; + +[TestFixture] +[NonParallelizable] +public class ReleaseMemoryHandlerTests +{ + [Test] + public async Task Should_Use_Aggressive_Collection_When_No_Workers_Are_Active() + { + FFmpegProcess.ProcessCount.ShouldBe(0); + + IFFmpegSegmenterService segmenterService = Substitute.For(); + segmenterService.Workers.Returns([]); + ILogger logger = Substitute.For>(); + var handler = new ReleaseMemoryHandler(segmenterService, logger); + + await handler.Handle(new ReleaseMemory(false), CancellationToken.None); + + ShouldHaveLogged(logger, "Starting aggressive garbage collection"); + } + + [Test] + public async Task Should_Use_Regular_Collection_When_A_Worker_Is_Active() + { + FFmpegProcess.ProcessCount.ShouldBe(0); + + IFFmpegSegmenterService segmenterService = Substitute.For(); + segmenterService.Workers.Returns([Substitute.For()]); + ILogger logger = Substitute.For>(); + var handler = new ReleaseMemoryHandler(segmenterService, logger); + + await handler.Handle(new ReleaseMemory(false), CancellationToken.None); + + ShouldHaveLogged(logger, "Starting garbage collection"); + } + + private static void ShouldHaveLogged(ILogger logger, string expectedMessage) => + logger.ReceivedCalls() + .Any(call => call.GetArguments().ElementAtOrDefault(2)?.ToString() == expectedMessage) + .ShouldBeTrue(); +} diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj index c94ffb3de..e3993490c 100644 --- a/ErsatzTV/ErsatzTV.csproj +++ b/ErsatzTV/ErsatzTV.csproj @@ -11,7 +11,6 @@ Debug;Release;Debug No Sync AnyCPU bf31217d-f4ec-4520-8cc3-138059044ede - latest-Recommended true true en-US diff --git a/docker/Dockerfile b/docker/Dockerfile index e3a989fb9..24e95ec85 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -45,6 +45,7 @@ COPY *.sln . # local/CI builds. Directory.Packages.props is REQUIRED here: with CPM the csproj # carry no versions, so restore fails without the central manifest. COPY Directory.Build.props Directory.Build.targets Directory.Packages.props global.json .editorconfig ./ +COPY eng/analyzers/sdk-all-suggestion.globalconfig ./eng/analyzers/ COPY artwork/* ./artwork/ COPY ErsatzTV/*.csproj ./ErsatzTV/ COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/ diff --git a/docs/ci-cd.md b/docs/ci-cd.md index d47d87b64..fe37447dd 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -299,27 +299,43 @@ running product from outside our C#/review stack. ## Static analysis & formatting -**Analyzer packs** — `Directory.Build.targets` references **Roslynator**, **SonarAnalyzer.CSharp**, -**Meziantou.Analyzer**, and **AsyncFixer** for every project (versions central via CPM; guarded on -`ManagePackageVersionsCentrally` so the gitignored `.mcp` tool isn't pulled in). They are introduced -**incrementally** (ersatztv#15): `.editorconfig` sets `dotnet_analyzer_diagnostic.severity = suggestion` -so the packs surface findings without failing the `TreatWarningsAsErrors` (TWAE) build. **Promotion is -the enforcement** — raising a rule to `warning` makes it a CI-blocking error via the existing TWAE -build, so no separate lint step is needed. +**Analyzers** — `Directory.Build.props` enables the SDK analyzers at `latest-All` and turns on +`Microsoft.VisualStudio.Threading.Analyzers` for every centrally managed project. +`Directory.Build.targets` also references +**Roslynator**, **SonarAnalyzer.CSharp**, **Meziantou.Analyzer**, and **AsyncFixer** repo-wide (versions +central via CPM). All analyzer package references are guarded on `ManagePackageVersionsCentrally`, so the +gitignored `.mcp` tool—which deliberately uses inline package versions—does not inherit versionless +references. +They are introduced **incrementally** (ersatztv#15). `eng/analyzers/sdk-all-suggestion.globalconfig` +enumerates the .NET 10 SDK `All` inventory at `suggestion`; this exact-ID baseline is necessary because +the SDK's generated `latest-All` severities outrank `.editorconfig` bulk settings. `.editorconfig` keeps +the threading and curated-pack baselines at `suggestion`. Diagnostics remain visible to IDEs and +`dotnet format analyzers`, but do not create a wall of failures (a direct `latest-All` trial activated +455 existing errors in the TWAE projects). + +**Promotion is the enforcement** — set a reviewed rule to `warning` in `.editorconfig` and append its ID +to the central `WarningsAsErrors` list in `Directory.Build.props`. The explicit list makes the rule block +in every project, including test projects that do not otherwise use TWAE. On a major SDK upgrade, +regenerate the checked-in SDK baseline from `analysislevel__all.globalconfig`, preserve SDK `none` +entries, and review newly introduced rules before accepting the snapshot. + +Promoted rules are recorded here so the blocking subset stays intentional and reviewable: +- **Sonar `S3981` — `warning` + `WarningsAsErrors`** (ersatztv#15): rejects collection-count comparisons that are constant + regardless of collection size. Its first finding exposed `Workers.Count >= 0`, which permanently + classified scheduled memory releases as busy and skipped the intended aggressive idle collection. - **StyleCop.Analyzers is intentionally excluded**: its latest stable (1.1.118) crashes (`AD0001`) on C# `record` declarations, and its rules overlap the existing `.editorconfig`/Roslynator. Revisit via the record-compatible `1.2.0-beta` only if specifically wanted. -- **Blazor `.razor` caveat**: editorconfig severity overrides don't reach analyzer diagnostics in Razor - `@code` (source-generator limitation — `dotnet format` can't fix them either), so the currently-firing - SonarAnalyzer rules are temporarily `NoWarn`-ed in `ErsatzTV.csproj` and burned down rule-by-rule in - **ersatztv#25**. The same rules run at `suggestion` on `.cs`. +- **The former Blazor `.razor` caveat is retired**: Blazor removal deleted the Razor sources and their + temporary Sonar `NoWarn` list. The `.razor`/`.cshtml` suggestion scopes remain in `.editorconfig` only + as a defensive default if server-rendered view code is ever reintroduced. -**Formatting** — the tree isn't yet `dotnet format`-clean (mixed UTF-8 BOM + whitespace inherited from -upstream: ~1,500 BOM files + ~480 whitespace). A one-time normalization lands as its **own dedicated -PR** (kept out of the analyzer work to stay reviewable); afterwards `dotnet format whitespace ---verify-no-changes` (+ `style`) joins the `test` job so drift can't return. `.gitattributes` already -pins line endings. +**Formatting** — the inherited tree still contains legacy UTF-8 BOM/whitespace debt, so the standing +policy is **format as you touch**, not a mass rewrite (ersatztv#311). The Husky pre-commit hook runs +`dotnet format --verify-no-changes` for staged C# files, and the blocking `format` CI job repeats that +check for C# files changed by the PR. Untouched legacy files remain outside the gate; `.gitattributes` +pins line endings. A one-time full-tree normalization remains a separate, unmade decision. ## Migration integrity (EF Core, both providers) diff --git a/docs/contributing.md b/docs/contributing.md index c4ddf6110..544444427 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -111,9 +111,13 @@ talks exclusively to the REST API. Every former Blazor route now 302-redirects t - **`TreatWarningsAsErrors=true`** in the app projects — a warning fails the build. `NoWarn` carries a small, documented exemption list (e.g. `VSTHRD200`, `CA1873`); NuGet-audit `NU1901-1903` are demoted to warnings in `Directory.Build.props` while `NU1904` (critical) blocks. -- **Static-analysis packs** (Roslynator, SonarAnalyzer, Meziantou, AsyncFixer) run at `suggestion` and - are promoted to `warning`/`error` rule-by-rule; promotion is the enforcement (the TWAE build). New - rules start at suggestion — never flip a wall of rules to error at once. (ersatztv#15) +- **Static analysis is centralized**: `Directory.Build.props` enables the SDK analyzers at + `latest-All` plus the threading analyzer for every centrally managed project; `Directory.Build.targets` + adds Roslynator, SonarAnalyzer, Meziantou, and AsyncFixer. Analyzer package references are CPM-guarded + so the gitignored `.mcp` tool retains its inline-version dependency model. The SDK globalconfig and + `.editorconfig` keep the broad baseline at `suggestion`. Promote one reviewed rule at a time by setting + it to `warning` and adding its ID to the central `WarningsAsErrors` list; never flip a wall of rules to error at once. + Refresh the SDK globalconfig deliberately when moving to a new .NET SDK major. (ersatztv#15) ## 8. Testing diff --git a/eng/analyzers/sdk-all-suggestion.globalconfig b/eng/analyzers/sdk-all-suggestion.globalconfig new file mode 100644 index 000000000..47eac0629 --- /dev/null +++ b/eng/analyzers/sdk-all-suggestion.globalconfig @@ -0,0 +1,292 @@ +# .NET 10 SDK analyzer baseline for ersatztv#15. +# +# AnalysisLevel=latest-All enables the complete SDK rule inventory, whose generated +# per-rule severities otherwise override .editorconfig's bulk suggestion setting. +# This higher-priority snapshot keeps every current .NET 10 rule visible but non-blocking. +# Preserve SDK 'none' entries for rules introduced after .NET 10. On an SDK major upgrade, +# regenerate this list from analysislevel__all.globalconfig and review new rules. +# A per-rule severity in the repository .editorconfig overrides this baseline. +is_global = true +global_level = -50 + +dotnet_diagnostic.CA1000.severity = suggestion +dotnet_diagnostic.CA1001.severity = suggestion +dotnet_diagnostic.CA1002.severity = suggestion +dotnet_diagnostic.CA1003.severity = suggestion +dotnet_diagnostic.CA1008.severity = suggestion +dotnet_diagnostic.CA1010.severity = suggestion +dotnet_diagnostic.CA1012.severity = suggestion +dotnet_diagnostic.CA1016.severity = suggestion +dotnet_diagnostic.CA1018.severity = suggestion +dotnet_diagnostic.CA1019.severity = suggestion +dotnet_diagnostic.CA1024.severity = suggestion +dotnet_diagnostic.CA1027.severity = suggestion +dotnet_diagnostic.CA1028.severity = suggestion +dotnet_diagnostic.CA1030.severity = suggestion +dotnet_diagnostic.CA1031.severity = suggestion +dotnet_diagnostic.CA1032.severity = suggestion +dotnet_diagnostic.CA1033.severity = suggestion +dotnet_diagnostic.CA1034.severity = suggestion +dotnet_diagnostic.CA1036.severity = suggestion +dotnet_diagnostic.CA1040.severity = suggestion +dotnet_diagnostic.CA1041.severity = suggestion +dotnet_diagnostic.CA1043.severity = suggestion +dotnet_diagnostic.CA1044.severity = suggestion +dotnet_diagnostic.CA1046.severity = suggestion +dotnet_diagnostic.CA1047.severity = suggestion +dotnet_diagnostic.CA1050.severity = suggestion +dotnet_diagnostic.CA1051.severity = suggestion +dotnet_diagnostic.CA1052.severity = suggestion +dotnet_diagnostic.CA1054.severity = suggestion +dotnet_diagnostic.CA1055.severity = suggestion +dotnet_diagnostic.CA1056.severity = suggestion +dotnet_diagnostic.CA1058.severity = suggestion +dotnet_diagnostic.CA1061.severity = suggestion +dotnet_diagnostic.CA1062.severity = suggestion +dotnet_diagnostic.CA1063.severity = suggestion +dotnet_diagnostic.CA1064.severity = suggestion +dotnet_diagnostic.CA1065.severity = suggestion +dotnet_diagnostic.CA1066.severity = suggestion +dotnet_diagnostic.CA1067.severity = suggestion +dotnet_diagnostic.CA1068.severity = suggestion +dotnet_diagnostic.CA1069.severity = suggestion +dotnet_diagnostic.CA1070.severity = suggestion +dotnet_diagnostic.CA1200.severity = suggestion +dotnet_diagnostic.CA1303.severity = suggestion +dotnet_diagnostic.CA1304.severity = suggestion +dotnet_diagnostic.CA1305.severity = suggestion +dotnet_diagnostic.CA1307.severity = suggestion +dotnet_diagnostic.CA1308.severity = suggestion +dotnet_diagnostic.CA1309.severity = suggestion +dotnet_diagnostic.CA1310.severity = suggestion +dotnet_diagnostic.CA1311.severity = suggestion +dotnet_diagnostic.CA1401.severity = suggestion +dotnet_diagnostic.CA1419.severity = suggestion +dotnet_diagnostic.CA1421.severity = suggestion +dotnet_diagnostic.CA1507.severity = suggestion +dotnet_diagnostic.CA1508.severity = suggestion +dotnet_diagnostic.CA1510.severity = suggestion +dotnet_diagnostic.CA1511.severity = suggestion +dotnet_diagnostic.CA1512.severity = suggestion +dotnet_diagnostic.CA1513.severity = suggestion +dotnet_diagnostic.CA1514.severity = suggestion +dotnet_diagnostic.CA1515.severity = suggestion +dotnet_diagnostic.CA1516.severity = none +dotnet_diagnostic.CA1700.severity = suggestion +dotnet_diagnostic.CA1707.severity = suggestion +dotnet_diagnostic.CA1708.severity = suggestion +dotnet_diagnostic.CA1710.severity = suggestion +dotnet_diagnostic.CA1711.severity = suggestion +dotnet_diagnostic.CA1712.severity = suggestion +dotnet_diagnostic.CA1713.severity = suggestion +dotnet_diagnostic.CA1715.severity = suggestion +dotnet_diagnostic.CA1716.severity = suggestion +dotnet_diagnostic.CA1720.severity = suggestion +dotnet_diagnostic.CA1721.severity = suggestion +dotnet_diagnostic.CA1724.severity = suggestion +dotnet_diagnostic.CA1725.severity = suggestion +dotnet_diagnostic.CA1727.severity = suggestion +dotnet_diagnostic.CA1802.severity = suggestion +dotnet_diagnostic.CA1805.severity = suggestion +dotnet_diagnostic.CA1806.severity = suggestion +dotnet_diagnostic.CA1810.severity = suggestion +dotnet_diagnostic.CA1812.severity = suggestion +dotnet_diagnostic.CA1813.severity = suggestion +dotnet_diagnostic.CA1814.severity = suggestion +dotnet_diagnostic.CA1815.severity = suggestion +dotnet_diagnostic.CA1816.severity = suggestion +dotnet_diagnostic.CA1819.severity = suggestion +dotnet_diagnostic.CA1820.severity = suggestion +dotnet_diagnostic.CA1821.severity = suggestion +dotnet_diagnostic.CA1822.severity = suggestion +dotnet_diagnostic.CA1823.severity = suggestion +dotnet_diagnostic.CA1824.severity = suggestion +dotnet_diagnostic.CA1825.severity = suggestion +dotnet_diagnostic.CA1826.severity = suggestion +dotnet_diagnostic.CA1827.severity = suggestion +dotnet_diagnostic.CA1828.severity = suggestion +dotnet_diagnostic.CA1829.severity = suggestion +dotnet_diagnostic.CA1830.severity = suggestion +dotnet_diagnostic.CA1832.severity = suggestion +dotnet_diagnostic.CA1833.severity = suggestion +dotnet_diagnostic.CA1834.severity = suggestion +dotnet_diagnostic.CA1835.severity = suggestion +dotnet_diagnostic.CA1836.severity = suggestion +dotnet_diagnostic.CA1837.severity = suggestion +dotnet_diagnostic.CA1838.severity = suggestion +dotnet_diagnostic.CA1839.severity = suggestion +dotnet_diagnostic.CA1840.severity = suggestion +dotnet_diagnostic.CA1841.severity = suggestion +dotnet_diagnostic.CA1842.severity = suggestion +dotnet_diagnostic.CA1843.severity = suggestion +dotnet_diagnostic.CA1844.severity = suggestion +dotnet_diagnostic.CA1845.severity = suggestion +dotnet_diagnostic.CA1846.severity = suggestion +dotnet_diagnostic.CA1847.severity = suggestion +dotnet_diagnostic.CA1848.severity = suggestion +dotnet_diagnostic.CA1849.severity = suggestion +dotnet_diagnostic.CA1850.severity = suggestion +dotnet_diagnostic.CA1851.severity = suggestion +dotnet_diagnostic.CA1852.severity = suggestion +dotnet_diagnostic.CA1853.severity = suggestion +dotnet_diagnostic.CA1854.severity = suggestion +dotnet_diagnostic.CA1855.severity = suggestion +dotnet_diagnostic.CA1856.severity = suggestion +dotnet_diagnostic.CA1858.severity = suggestion +dotnet_diagnostic.CA1859.severity = suggestion +dotnet_diagnostic.CA1860.severity = suggestion +dotnet_diagnostic.CA1861.severity = suggestion +dotnet_diagnostic.CA1862.severity = suggestion +dotnet_diagnostic.CA1863.severity = suggestion +dotnet_diagnostic.CA1864.severity = suggestion +dotnet_diagnostic.CA1865.severity = suggestion +dotnet_diagnostic.CA1866.severity = suggestion +dotnet_diagnostic.CA1867.severity = suggestion +dotnet_diagnostic.CA1868.severity = suggestion +dotnet_diagnostic.CA1869.severity = suggestion +dotnet_diagnostic.CA1870.severity = suggestion +dotnet_diagnostic.CA1871.severity = suggestion +dotnet_diagnostic.CA1872.severity = suggestion +dotnet_diagnostic.CA1873.severity = suggestion +dotnet_diagnostic.CA1874.severity = suggestion +dotnet_diagnostic.CA1875.severity = suggestion +dotnet_diagnostic.CA2000.severity = suggestion +dotnet_diagnostic.CA2002.severity = suggestion +dotnet_diagnostic.CA2007.severity = suggestion +dotnet_diagnostic.CA2008.severity = suggestion +dotnet_diagnostic.CA2009.severity = suggestion +dotnet_diagnostic.CA2011.severity = suggestion +dotnet_diagnostic.CA2012.severity = suggestion +dotnet_diagnostic.CA2016.severity = suggestion +dotnet_diagnostic.CA2019.severity = suggestion +dotnet_diagnostic.CA2020.severity = suggestion +dotnet_diagnostic.CA2025.severity = suggestion +dotnet_diagnostic.CA2100.severity = suggestion +dotnet_diagnostic.CA2101.severity = suggestion +dotnet_diagnostic.CA2119.severity = suggestion +dotnet_diagnostic.CA2153.severity = suggestion +dotnet_diagnostic.CA2201.severity = suggestion +dotnet_diagnostic.CA2207.severity = suggestion +dotnet_diagnostic.CA2208.severity = suggestion +dotnet_diagnostic.CA2211.severity = suggestion +dotnet_diagnostic.CA2213.severity = suggestion +dotnet_diagnostic.CA2214.severity = suggestion +dotnet_diagnostic.CA2215.severity = suggestion +dotnet_diagnostic.CA2216.severity = suggestion +dotnet_diagnostic.CA2217.severity = suggestion +dotnet_diagnostic.CA2218.severity = suggestion +dotnet_diagnostic.CA2219.severity = suggestion +dotnet_diagnostic.CA2224.severity = suggestion +dotnet_diagnostic.CA2225.severity = suggestion +dotnet_diagnostic.CA2226.severity = suggestion +dotnet_diagnostic.CA2227.severity = suggestion +dotnet_diagnostic.CA2231.severity = suggestion +dotnet_diagnostic.CA2234.severity = suggestion +dotnet_diagnostic.CA2235.severity = suggestion +dotnet_diagnostic.CA2237.severity = suggestion +dotnet_diagnostic.CA2241.severity = suggestion +dotnet_diagnostic.CA2242.severity = suggestion +dotnet_diagnostic.CA2243.severity = suggestion +dotnet_diagnostic.CA2244.severity = suggestion +dotnet_diagnostic.CA2245.severity = suggestion +dotnet_diagnostic.CA2246.severity = suggestion +dotnet_diagnostic.CA2248.severity = suggestion +dotnet_diagnostic.CA2249.severity = suggestion +dotnet_diagnostic.CA2250.severity = suggestion +dotnet_diagnostic.CA2251.severity = suggestion +dotnet_diagnostic.CA2252.severity = suggestion +dotnet_diagnostic.CA2253.severity = suggestion +dotnet_diagnostic.CA2254.severity = suggestion +dotnet_diagnostic.CA2262.severity = suggestion +dotnet_diagnostic.CA2263.severity = suggestion +dotnet_diagnostic.CA2300.severity = suggestion +dotnet_diagnostic.CA2301.severity = suggestion +dotnet_diagnostic.CA2302.severity = suggestion +dotnet_diagnostic.CA2305.severity = suggestion +dotnet_diagnostic.CA2310.severity = suggestion +dotnet_diagnostic.CA2311.severity = suggestion +dotnet_diagnostic.CA2312.severity = suggestion +dotnet_diagnostic.CA2315.severity = suggestion +dotnet_diagnostic.CA2321.severity = suggestion +dotnet_diagnostic.CA2322.severity = suggestion +dotnet_diagnostic.CA2326.severity = suggestion +dotnet_diagnostic.CA2327.severity = suggestion +dotnet_diagnostic.CA2328.severity = suggestion +dotnet_diagnostic.CA2329.severity = suggestion +dotnet_diagnostic.CA2330.severity = suggestion +dotnet_diagnostic.CA2350.severity = suggestion +dotnet_diagnostic.CA2351.severity = suggestion +dotnet_diagnostic.CA2352.severity = suggestion +dotnet_diagnostic.CA2353.severity = suggestion +dotnet_diagnostic.CA2354.severity = suggestion +dotnet_diagnostic.CA2355.severity = suggestion +dotnet_diagnostic.CA2356.severity = suggestion +dotnet_diagnostic.CA2361.severity = suggestion +dotnet_diagnostic.CA2362.severity = suggestion +dotnet_diagnostic.CA3001.severity = suggestion +dotnet_diagnostic.CA3002.severity = suggestion +dotnet_diagnostic.CA3003.severity = suggestion +dotnet_diagnostic.CA3004.severity = suggestion +dotnet_diagnostic.CA3005.severity = suggestion +dotnet_diagnostic.CA3006.severity = suggestion +dotnet_diagnostic.CA3007.severity = suggestion +dotnet_diagnostic.CA3008.severity = suggestion +dotnet_diagnostic.CA3009.severity = suggestion +dotnet_diagnostic.CA3010.severity = suggestion +dotnet_diagnostic.CA3011.severity = suggestion +dotnet_diagnostic.CA3012.severity = suggestion +dotnet_diagnostic.CA3061.severity = suggestion +dotnet_diagnostic.CA3075.severity = suggestion +dotnet_diagnostic.CA3076.severity = suggestion +dotnet_diagnostic.CA3077.severity = suggestion +dotnet_diagnostic.CA3147.severity = suggestion +dotnet_diagnostic.CA5350.severity = suggestion +dotnet_diagnostic.CA5351.severity = suggestion +dotnet_diagnostic.CA5358.severity = suggestion +dotnet_diagnostic.CA5359.severity = suggestion +dotnet_diagnostic.CA5360.severity = suggestion +dotnet_diagnostic.CA5361.severity = suggestion +dotnet_diagnostic.CA5362.severity = suggestion +dotnet_diagnostic.CA5363.severity = suggestion +dotnet_diagnostic.CA5364.severity = suggestion +dotnet_diagnostic.CA5365.severity = suggestion +dotnet_diagnostic.CA5366.severity = suggestion +dotnet_diagnostic.CA5367.severity = suggestion +dotnet_diagnostic.CA5368.severity = suggestion +dotnet_diagnostic.CA5369.severity = suggestion +dotnet_diagnostic.CA5370.severity = suggestion +dotnet_diagnostic.CA5371.severity = suggestion +dotnet_diagnostic.CA5372.severity = suggestion +dotnet_diagnostic.CA5373.severity = suggestion +dotnet_diagnostic.CA5374.severity = suggestion +dotnet_diagnostic.CA5375.severity = suggestion +dotnet_diagnostic.CA5376.severity = suggestion +dotnet_diagnostic.CA5377.severity = suggestion +dotnet_diagnostic.CA5378.severity = suggestion +dotnet_diagnostic.CA5379.severity = suggestion +dotnet_diagnostic.CA5380.severity = suggestion +dotnet_diagnostic.CA5381.severity = suggestion +dotnet_diagnostic.CA5382.severity = suggestion +dotnet_diagnostic.CA5383.severity = suggestion +dotnet_diagnostic.CA5384.severity = suggestion +dotnet_diagnostic.CA5385.severity = suggestion +dotnet_diagnostic.CA5386.severity = suggestion +dotnet_diagnostic.CA5387.severity = suggestion +dotnet_diagnostic.CA5388.severity = suggestion +dotnet_diagnostic.CA5389.severity = suggestion +dotnet_diagnostic.CA5390.severity = suggestion +dotnet_diagnostic.CA5391.severity = suggestion +dotnet_diagnostic.CA5392.severity = suggestion +dotnet_diagnostic.CA5393.severity = suggestion +dotnet_diagnostic.CA5394.severity = suggestion +dotnet_diagnostic.CA5395.severity = suggestion +dotnet_diagnostic.CA5396.severity = suggestion +dotnet_diagnostic.CA5397.severity = suggestion +dotnet_diagnostic.CA5398.severity = suggestion +dotnet_diagnostic.CA5399.severity = suggestion +dotnet_diagnostic.CA5400.severity = suggestion +dotnet_diagnostic.CA5401.severity = suggestion +dotnet_diagnostic.CA5402.severity = suggestion +dotnet_diagnostic.CA5403.severity = suggestion +dotnet_diagnostic.CA5404.severity = suggestion +dotnet_diagnostic.CA5405.severity = suggestion