dotnet-analyzers
Skill2 starsChanged 42 days agoMITView on GitHub
Use when adding, configuring, or reviewing Roslyn analyzers in a .NET repo — StyleSharp/PerformanceSharp/SecuritySharp (or an equivalent first-party analyzer set), the built-in CAxxxx code-quality rules, IDExxxx code-style rules, and SYSLIBxxxx obsoletion/source-generator diagnostics. Covers wiring analyzers in, severity precedence in .editorconfig, fixing vs. suppressing, and picking the highest-value rules to turn on.
In this repository56 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- benchmarkingSkill
- commit-messagesSkill
- csharp-15-syntaxSkill
- csharp-aot-trimmingSkill
- csharp-api-designSkill
- csharp-assembly-inspectionSkill
- csharp-asyncSkill
- csharp-collections-modernSkill
- csharp-concurrency-primitivesSkill
- csharp-data-layoutSkill
- csharp-debuggingSkill
- csharp-discardsSkill
- csharp-disposable-patternsSkill
- csharp-docsSkill
- csharp-error-handlingSkill
- csharp-extension-membersSkill
- csharp-generics-modernSkill
- csharp-guard-clausesSkill
- csharp-json-source-generationSkill
- csharp-language-versionsSkill
- csharp-logging-diagnosticsSkill
- csharp-lspSkill
- csharp-modern-typesSkill
- csharp-nullabilitySkill
- csharp-nullable-migrationSkill
- csharp-nunitSkill
- csharp-pattern-matchingSkill
- csharp-performanceSkill
- csharp-polyfillsSkill
- csharp-source-generatorsSkill
- csharp-static-lambdasSkill
- csharp-string-handlingSkill
- csharp-tunitSkill
- csharp-verificationSkill
- dispatching-subagentsSkill
- dotnet-11-librariesSkill
- dotnet-central-package-managementSkill
- dotnet-file-based-appsSkill
- dotnet-msbuild-usingsSkill
- dotnet-multi-targetingSkill
- dotnet-project-hygieneSkill
- dotnet-runSkill
- dotnet-runtime-asyncSkill
- dotnet-slnxSkill
- dotnet-test-platformsSkill
- executing-plansSkill
- performance-bounded-deduplicationSkill
- roslyn-analyzer-performanceSkill
- roslyn-discoverySkill
- roslyn-duplicate-detectionSkill
- roslyn-guard-rewritersSkill
- roslyn-rewritersSkill
- subagent-driven-developmentSkill
- tddSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 140
Don't put root = true in the nested file — it must inherit, not replace, the repo-root config.
- Distinctive: line 141
Every relaxation gets a comment saying *why* it's fine in tests, same discipline as any other suppression.
- Very specific: line 252
If you're writing a first-party analyzer (see roslyn-analyzer-performance for making its hot paths cheap), its diagnostic message and docs …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
.NET analyzers: wiring, configuring, and fixing
An analyzer that isn't wired to fail the build is a linter nobody reads. Getting this right is
three separate jobs: add the analyzer packages so they run, configure severities so the
right things fail the build, and fix what they find instead of making them quiet. See
dotnet-project-hygiene for the AnalysisLevel/TreatWarningsAsErrors basics this goes deeper
on, and csharp-verification for why an analyzer warning is a failure, not noise.
Adding analyzers
Reference analyzer packages the same way across every project with central package management:
version in Directory.Packages.props, the actual PackageReference once in Directory.Build.props
so every project that imports it gets the analyzer, PrivateAssets="all" so the analyzer doesn't
flow to your own package's consumers (it's a build-time tool, not a runtime dependency):
<!-- Directory.Packages.props -->
<PropertyGroup>
<!-- StyleSharp.Analyzers, PerformanceSharp.Analyzers and SecuritySharp.Analyzers ship from the
same release pipeline and always share a version. -->
<RoslynCommonAnalyzersVersion>5.1.2</RoslynCommonAnalyzersVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="StyleSharp.Analyzers" Version="$(RoslynCommonAnalyzersVersion)" />
<PackageVersion Include="PerformanceSharp.Analyzers" Version="$(RoslynCommonAnalyzersVersion)" />
<PackageVersion Include="SecuritySharp.Analyzers" Version="$(RoslynCommonAnalyzersVersion)" />
</ItemGroup>
<!-- Directory.Build.props -->
<ItemGroup>
<PackageReference Include="StyleSharp.Analyzers" PrivateAssets="all"/>
<PackageReference Include="PerformanceSharp.Analyzers" PrivateAssets="all"/>
<PackageReference Include="SecuritySharp.Analyzers" PrivateAssets="all"/>
<PackageReference Include="Roslynator.Analyzers" PrivateAssets="All"/>
<PackageReference Include="SonarAnalyzer.CSharp" PrivateAssets="all"/>
</ItemGroup>A plain PackageReference (version omitted) inside Directory.Build.props is enough under CPM —
you don't need GlobalPackageReference here; that item exists for packages every project needs
even when a project doesn't otherwise reference anything (see dotnet-central-package-management).
Both forms need PrivateAssets="all" explicitly; CPM does not imply it.
Turn on the rest of the pipeline in the same Directory.Build.props:
<PropertyGroup>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>All</AnalysisMode> <!-- or AllEnabledByDefault, Recommended -->
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>With AnalysisMode=All and EnforceCodeStyleInBuild=true, both a CA rule (CA1305) and an IDE
rule (IDE0008) fire on a plain dotnet build with no .editorconfig entry for either —
AnalysisMode is the fallback bucket for anything the file doesn't mention.
Adopting the RoslynCommonAnalyzers presets
StyleSharp/PerformanceSharp/SecuritySharp ship ready-to-use presets at their repo root —
recommended.editorconfig, recommended-performancesharp.editorconfig,
recommended-securitysharp.editorconfig — each listing every rule at its default severity, with
opt-in rules commented out. Per the packages' own docs/CONFIGURATION.md: copy one in, or merge
its [*.cs] block into your existing .editorconfig, then tune. They configure entirely through
.editorconfig — no separate JSON file — using the same AnalyzerConfigOptionsProvider mechanism
the .NET SDK's own CA analyzers use, with a per-package option prefix (stylesharp.,
performancesharp., securitysharp.) mirroring CA's dotnet_code_quality. prefix. Rule-specific
keys (stylesharp.SST1315.union_member_naming) win over the general one
(stylesharp.union_member_naming).
Configuring: severity and precedence
Severity lives in .editorconfig, per rule:
[*.cs]
dotnet_diagnostic.CA1062.severity = warning
dotnet_diagnostic.IDE0060.severity = suggestionCategory-level fallback (dotnet_analyzer_diagnostic.category-<Category>.severity) sets a default
for every rule in that category that isn't individually overridden — useful for "everything in
Naming is a suggestion by default" without writing 40 lines. A per-rule
dotnet_diagnostic.<ID>.severity always wins over it — but see the precedence caveat below before
relying on it: an explicit AnalysisMode changes whether it does anything at all.
Precedence:
dotnet_diagnostic.<ID>.severity = none/NoWarn— the rule never fires, full stop.<NoWarn>$(NoWarn);CA1822</NoWarn>plusTreatWarningsAsErrors=truebuilds clean on a file that triggersCA1822—NoWarnwins over warnings-as-errors, it isn't a race.dotnet_diagnostic.<ID>.severity = <level>in.editorconfigsets that rule's severity regardless ofAnalysisMode. SettingCA1822tosuggestionin.editorconfigremoves it from the build-warning list even underAnalysisMode=Recommended.AnalysisMode/AnalysisLevel, whenAnalysisModeis set explicitly (Recommended,All, …) — this computes a per-rule severity that outranks category-level fallback. WithAnalysisMode=Allset,dotnet_analyzer_diagnostic.category-Performance.severity = errorhas no effect onCA1822(stayswarning) — an explicitAnalysisModeis not just a fallback bucket, it wins over category-level severity for rules it computes a severity for.dotnet_analyzer_diagnostic.category-<Category>.severity— only actually takes effect as documented (a default for every rule in that category with no explicitdotnet_diagnostic.<ID>entry) whenAnalysisModeis left unset at its SDK default. RemovingAnalysisModeentirely from the same project makes the identicaldotnet_analyzer_diagnostic.category-Performance.severity = errorpromoteCA1822toerror. If yourDirectory.Build.propssetsAnalysisModeexplicitly — which this skill recommends — category-level severity is not a reliable lever for CA rules; use per-ruledotnet_diagnostic.<ID>.severityinstead.TreatWarningsAsErrors/WarningsAsErrors— promotes whatever severity survived steps 1–4 from warning to error. It cannot resurrect a rule steps 1–2 silenced.
So: NoWarn and .editorconfig per-rule severity are suppressions/overrides that happen before
warnings-as-errors is even consulted, and both outrank an explicit AnalysisMode. Dropping a
rule's severity to none and adding it to NoWarn do the same thing through different doors —
both need the same justification as a #pragma, not a reflex edit. Don't reach for category-level
severity as your primary lever in a repo that sets AnalysisMode explicitly; it silently no-ops for
rules AnalysisMode already assigns a severity to.
Test-directory relaxations
A nested .editorconfig under src/tests/ narrows scope for rules that don't fit test code —
CA1812 (uninstantiated internal class — that's every test fixture), CA2213/CA1063 (dispose
patterns that don't matter when the process exits after the run), or a project-specific analyzer's
hot-path rule:
# src/tests/.editorconfig — inherits the repo-root .editorconfig; only lists test-specific overrides
[*.cs]
dotnet_diagnostic.CA1812.severity = none # Avoid uninstantiated internal classes
dotnet_diagnostic.CA2213.severity = none # Disposable fields should be disposed
performancesharp.avoid_linq_on_hot_path = false
dotnet_diagnostic.PSH1100.severity = none # Tests can use LINQ for clarityDon't put root = true in the nested file — it must inherit, not replace, the repo-root config.
Every relaxation gets a comment saying why it's fine in tests, same discipline as any other
suppression.
Grouping convention in a large .editorconfig
Real .editorconfig files sort CA rules by numeric ID within a category comment block, and every
none/non-default severity carries an inline reason — either a real false-positive-for-us reason,
or "this rule is redundant with another one this repo treats as canonical":
###################
# CA10xx - Design
###################
dotnet_diagnostic.CA1000.severity = none # Do not declare static members on generic types — common factory pattern
dotnet_diagnostic.CA1001.severity = error # Types that own disposable fields should be disposable
dotnet_diagnostic.CA1051.severity = none # Duplicate of SST1401 (canonical) — do not declare visible instance fields
dotnet_diagnostic.CA1822.severity = none # Mark members as static — covered by PSH1414
dotnet_diagnostic.CA1860.severity = none # Avoid using 'Enumerable.Any()' extension method — covered by PSH1103That "covered by PSH#### (canonical)" pattern is deliberate: when a first-party analyzer and a
CA/Roslynator/SonarAnalyzer rule overlap, pick one as canonical (usually the one with the better
fixer or the more specific message) and silence the other with a comment naming its replacement —
never leave both live reporting the same defect twice.
SYSLIB rules get their own grouped block, same pattern:
# Microsoft .NET Runtime Obsoletions (SYSLIB0xxx)
dotnet_diagnostic.SYSLIB0011.severity = error # BinaryFormatter serialization is obsolete
...
# GeneratedRegex source generator
dotnet_diagnostic.SYSLIB1045.severity = error # Convert to 'GeneratedRegexAttribute' for compile-time regex generation
# LibraryImport (P/Invoke) source generator
dotnet_diagnostic.SYSLIB1054.severity = error # Use 'LibraryImportAttribute' instead of 'DllImportAttribute' ...Fixing, not suppressing
Fix the cause. A suppression is for a genuine false positive, scoped as narrowly as possible, with a comment saying why:
// The connection string's empty password is the fixture the secret-detection rule is measured
// against, not a real credential.
#pragma warning disable SES1203
private const string TestConnectionString = "Server=.;Password=;";
#pragma warning restore SES1203[SuppressMessage] for a suppression that belongs to a whole member rather than a few lines, with
the same non-negotiable Justification. Never drop a rule's severity in .editorconfig just to
quiet one call site — that silences it everywhere, not just where you looked.
Apply a fixer to exactly the files you touched, one rule at a time, instead of reformatting the tree:
dotnet format analyzers --include src/Orders/OrderService.cs --diagnostics SYSLIB1045
dotnet format style --include src/Orders/OrderService.cs --diagnostics IDE0008See csharp-verification for the full gate (dotnet build clean, dotnet format --verify-no-changes
clean, suppressions never used to force green).
High-value rules worth turning on
These come from .editorconfig blocks actually enabled at error/warning across multiple
first-party repos surveyed for this skill (reactiveui, splat, Akavache, refit,
Fusillade, Primitives):
CA1848(useLoggerMessagedelegates) —erroreverywhere surveyed. Seecsharp-logging-diagnosticsfor the[LoggerMessage]pattern it wants instead ofILogger.LogInformation(...).CA1873(avoid expensiveDebug.Assert/logging argument evaluation) — guard the argument withIsEnabledrather than computing it unconditionally; seecsharp-logging-diagnostics.CA2007(ConfigureAwait(false)) — consistently disabled in these repos with the same reasoning each time: "Rx and library callers drive synchronization context themselves." Don't cargo-cult this rule on; decide it for your own library's actual callers.CA1305/CA1307/CA1309/CA1310(culture-sensitive string operations, culture-sensitive comparison) — real bugs in anything building cache keys, URLs, or persisted data fromToString()/ToLower()without an explicitCultureInfo/StringComparison. Write instead:value.ToString(CultureInfo.InvariantCulture),string.Equals(a, b, StringComparison.Ordinal).CA1822(mark members static) — frequently disabled in favor of an equivalent first-party rule (PSH1414in the surveyed repos) rather than left off; check whether your analyzer set has a canonical version before just enabling CA's.CA1851(possible multiple enumeration ofIEnumerable) — real perf/correctness bug when a lazy sequence is enumerated twice (e.g., once forCount(), once for aforeach) and the source isn't idempotent. Materialize once:var items = source.ToList();.CA1859/CA1860/CA1861/CA1865–CA1867— cheaper API selection (CA1859: use the concrete type instead of the interface when nothing needs the abstraction;CA1860:.Count == 0over.Any()on a collection;CA1861: hoist a constant array literal out of a hot call site;CA1865–CA1867: thecharoverloads ofStartsWith/EndsWith/IndexOf). Thecharoverloads don't exist onnetstandard2.0/.NET Framework — repos multi-targeting those disableCA1865–CA1867for exactly that reason; don't blanket-enable them without checking your TFMs.CA2263(prefer the generic overload when the type is known at the call site) — replacesGetService(typeof(T))-shaped calls withGetService<T>().SYSLIB0xxx(runtime obsoletions —BinaryFormatter, CAS,Thread.Abort, weak crypto defaults, …) —error, notwarning, in every repo surveyed. These mark APIs the runtime no longer supports correctly; there is no "later" for these, only "someone hits it in production."SYSLIB1045([GeneratedRegex]instead ofnew Regex(...)/Regex.IsMatchwith a constant pattern) andSYSLIB1054([LibraryImport]instead of[DllImport]) — both source-generator suggestions, botherrorin the surveyed repos. Seecsharp-aot-trimmingfor why these matter beyond style: the generated code is trim/AOT-safe where reflection-basedRegex/P/Invoke marshalling is not.SYSLIB1030(JsonSourceGeneratordid not generate metadata for a type reachable from a[JsonSerializable]root) — a real gap in aJsonSerializerContext, not noise; seecsharp-json-source-generation.
Writing your own analyzer: describe the defect, not another tool's rule ID
If you're writing a first-party analyzer (see roslyn-analyzer-performance for making its hot
paths cheap), its diagnostic message and docs describe the defect in its own terms — "this
sequence is enumerated twice" — not "see CA1851" or "duplicate of SonarAnalyzer S1234". A consumer
without that other tool installed still needs the message to make sense, and the .editorconfig
comment convention above (# covered by PSH1103) is where cross-tool relationships belong — in
the consuming repo's config, not in the rule's own shipped text.
Checklist
- Analyzer packages referenced once (
Directory.Packages.propsversion +Directory.Build.propsPackageReference PrivateAssets="all"), not per-project AnalysisLevel=latest,AnalysisModepicked deliberately (RecommendedvsAll),EnforceCodeStyleInBuild=true,TreatWarningsAsErrors=true- First-party preset (
recommended*.editorconfig) adopted or merged, not reinvented rule by rule - Every non-default severity in
.editorconfighas an inline reason, especially a "canonical rule" cross-reference when two analyzers cover the same defect - Test-directory
.editorconfigrelaxations are scoped to the test tree, inherit (noroot = true), and each line says why - No repo-wide
NoWarnused to silence a rule that should be fixed; suppressions are scoped and justified - SYSLIB0xxx obsoletions and SYSLIB1045/1054/1030 source-gen suggestions are enabled, not left at their (often lower) shipped default
dotnet format analyzers --include <file> --diagnostics <ID>used to apply one fixer to exactly the changed files, not a baredotnet format- Verified with a real build, per
csharp-verification— not assumed from reading the rule
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

