The compiler and the .NET analyzers ship thousands of rules and enable a few hundred of them. Which ones run, how loudly they speak, and where that is decided is project configuration, and it belongs in the repository next to the code.
Turn analysis on in the build, for every project
EnableNETAnalyzers is what puts the .NET analyzers into a build, and AnalysisLevel decides which set of rules that is. Set both in Directory.Build.props so every project is analyzed the same way instead of depending on which template created it.
Preferred
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest-recommended</AnalysisLevel>
</PropertyGroup>Avoid
<!-- analysis on in this project, left at the template default in the next -->Corpus: Overview of .NET source code analysis · Configuration files for code analysis rules
Make warnings fail the build you gate on
A warning nobody reads is a comment. TreatWarningsAsErrors turns the analyzer output into a build failure, and EnforceCodeStyleInBuild extends the same treatment to the style rules, which are otherwise IDE-only.
Preferred
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>Avoid
<!-- the CI build prints 400 warnings and exits 0 -->Decide severity as a value, not as on or off
Every rule has a severity on a ladder that runs none, silent, suggestion, warning, error. Moving a rule down that ladder is a decision about the codebase; turning it off entirely throws the finding away.
Preferred
dotnet_diagnostic.CA1031.severity = error
dotnet_diagnostic.CA1062.severity = suggestionAvoid
dotnet_diagnostic.CA1031.severity = none # the real fix was to stop catching ExceptionOne rule, one category, or all of them
The same setting scopes to a single rule, to a whole category, or to everything. A category setting only moves the rules that category enables by default, which is what a "raise the security rules" decision wants.
Preferred
dotnet_diagnostic.CA2007.severity = warning
dotnet_analyzer_diagnostic.category-Security.severity = warningAvoid
dotnet_analyzer_diagnostic.severity = error # every finding is now a build failureWrite the configuration in .editorconfig
EditorConfig is where analyzer severity, style options and per-path exceptions all live. The nearest file to the source wins, and a section header such as [src/**/*.cs] scopes a whole folder, so a rule can be strict in new code and quiet in a legacy tree.
Preferred
[*.cs]
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
[src/legacy/**/*.cs]
dotnet_diagnostic.CA1031.severity = silentAvoid
# the same rules configured differently in five project filesCorpus: Configuration files for code analysis rules · Configuration options for code analysis
Use a global analyzer config for repository defaults
A .globalconfig file applies settings to every project that references it, which is how a repository states its defaults once instead of copying an .editorconfig into every project directory.
Style options carry their own severity
A style preference such as csharp_style_var_for_built_in_types is written as value:severity. Without the severity the option only informs the IDE; with it, the rule reports in a build that has EnforceCodeStyleInBuild.
Preferred
csharp_style_var_when_type_is_apparent = true:warningAvoid
csharp_style_var_when_type_is_apparent = true # read by the IDE, ignored by the buildCorpus: Code-style rule options
Quality rules have their own options
Beyond severity, some quality rules read a configuration option, and many of those options can be set per symbol rather than for the rule as a whole. Read the rule page before suppressing it: the option is often the intended dial.
Suppress at the narrowest scope that works
A preprocessor directive covers the lines around it, an attribute covers a member. Both should name the rule id and sit as close to the exception as the code allows, because a suppression with no rule id outlives its reason.
Preferred
#pragma warning disable CA1031 // the caller decides what a failed poll means
try { await PollAsync(ct); } catch (Exception ex) { _log.Warn(ex); }
#pragma warning restore CA1031Avoid
#pragma warning disable // everything, for the rest of the fileTurn a rule off in .editorconfig, not in NoWarn
dotnet_diagnostic.<id>.severity = none says which rule, in which scope, and it shows up in review. NoWarn hides the id inside a project file, where the next reader will not find it next to the code it concerns.
Avoid
<NoWarn>$(NoWarn);CA1707</NoWarn>Categories say what a rule is for
Every rule belongs to one category such as Design, Globalization, Security or Usage. The category is the shortest answer to "why is this rule complaining", and it is what catalog pages and category-wide severities are organised by.
Corpus: Rule categories
Adopt new rules deliberately
When a .NET version adds rules, the predefined configurations show what Microsoft turns on by default. Raising the analysis level and reviewing what appears is a smaller job than discovering it after a surprise upgrade.
Corpus: Predefined configuration files