An opinionated set of Roslyn Analyzers for C# projects that enforce strict coding standards and best practices.
$ dotnet add package StrictCSharp.AnalyzersA set of strict .NET Compiler Platform (Roslyn) Analyzers for C# projects.
Install the package via NuGet:
dotnet add package StrictCSharp.Analyzers
Or add it to your .csproj file:
<ItemGroup>
<PackageReference Include="StrictCSharp.Analyzers" Version="0.0.2" />
</ItemGroup>
All analyzers are configured as errors by default to enforce strict coding standards. See Configuration for more details on how to disable or change the severity of individual analyzers.
Definition: Requires all public types to have XML documentation.
Motivation: Public APIs should explain both what they do and why they do it, to help consumers understand how to use them. This is especially important in the age of AI-generated code, for helping both humans and AIs with understanding.
While excessive documentation can be hard to maintain, requiring XML documentation on only public types provides good value with minimal maintenance effort.
Definition: Prohibits type names ending with generic suffixes like 'Service', 'Manager', 'Helper', or 'Util'.
Motivation: These suffixes do not convey meaningful information about what the class actually does, and promote classes with multiple responsibilities. If you find yourself using a weasel word, you should think of a better abstraction.
Definition: Requires using statement expressions instead of using blocks when the using is the last statement in a method.
Motivation: Using statement expressions (using var x = ...) are more concise and reduce nesting compared to using blocks (using (var x = ...) { ... }). When the using is the last statement, the block adds unnecessary indentation without providing value.
Definition: Requires using Ardalis.GuardClauses for parameter validation instead of manual if-throw patterns.
Motivation: Guard clauses provide a concise and consistent way to validate parameters. The Ardalis.GuardClauses library is the most well fleshed-out and widely used guard clause library in the .NET ecosystem.
Definition: Prohibits using nameof for the second parameter in Guard.Against calls.
Motivation: The Ardalis.GuardClauses library uses CallerArgumentExpression on the second parameter to automatically capture the expression name. Using nameof is redundant and unnecessary.
Definition: Prohibits inline comments (// or /* */).
Motivation: Code should be self-explanatory. If you need a comment to explain what code does, consider extracting it into a well-named method instead. This is especially important in the age of AI-generated code, because LLMs have a tendency to sprinkle comments absolutely everywhere.
For public APIs, use XML documentation comments instead.
Definition: Requires using FluentAssertions instead of xUnit's Assert methods.
Motivation: FluentAssertions provides more readable assertions with better error messages. Compare Assert.Equal(expected, actual) to actual.Should().Be(expected). FluentAssertions also provides detailed failure messages that show exactly what went wrong.
Definition: Prohibits await Task.Delay() in tests.
Motivation: Unit tests should be fast and deterministic. Task.Delay() makes tests slow and can introduce flakiness due to timing issues.
Definition: Requires test methods to have // Arrange, // Act, and // Assert comments marking each section.
Motivation: The Arrange-Act-Assert pattern makes tests easier to read and understand. Explicit comments enforce this structure and help readers quickly identify what's being set up, what action is being tested, and what's being verified. Additionally, explicit sections denoted by comments make it easy to see when one of the sections is getting too large, and could benefit from refactoring.
Definition: Requires using AssertionScope when a test method has multiple FluentAssertions.
Motivation: Without AssertionScope, tests stop at the first failed assertion. With AssertionScope, all assertions are evaluated and you see all failures at once, making it faster to fix multiple issues.
Definition: Requires test classes to have a [Trait("Category", ...)] attribute.
Motivation: Categorizing tests allows you to run specific subsets of tests (e.g., unit tests vs integration tests). This also helps developers understand which tests are unit tests and should not make external calls, and which tests are integration tests and can make external calls.
Definition: Requires test classes to end with the suffix "Tests".
Motivation: Consistent and explicit naming conventions make it easy to identify test classes at a glance.
Definition: Requires test methods to follow the 3-segment naming pattern: MethodName_Scenario_ExpectedResult.
Motivation: This naming convention makes test intent crystal clear. For example, Authenticate_WithInvalidPassword_ThrowsAuthenticationException tells you exactly what's being tested, under what conditions, and what should happen.
Definition: Requires test classes to have a [TestOf(typeof(...))] attribute.
Motivation: This attribute explicitly links test classes to the code they test, making it easy to find tests for a given class and ensuring tests are organized around the code under test.
Definition: Requires test class namespaces to match the namespace of the class under test.
Motivation: Mirroring namespaces between production code and tests makes it easy to navigate between them, and easy to find which classes are missing tests.
All analyzers are enabled by default when you install the package. You can selectively enable or disable analyzers using an .editorconfig file in your project.
To disable specific analyzers, add entries to your .editorconfig file:
root = true
[*.cs]
# Disable specific analyzers by their diagnostic ID
dotnet_diagnostic.SC021.severity = none # Type documentation
dotnet_diagnostic.SC041.severity = none # Weasel word names
dotnet_diagnostic.SC141.severity = none # Inline comments
You can disable entire categories of analyzers:
[*.cs]
# Disable all testing rules
dotnet_analyzer_diagnostic.category-Testing.severity = none
# Disable all documentation rules
dotnet_analyzer_diagnostic.category-Documentation.severity = none
# Disable all naming rules
dotnet_analyzer_diagnostic.category-Naming.severity = none
# Disable all style rules
dotnet_analyzer_diagnostic.category-Style.severity = none
You can also change the severity of analyzers instead of disabling them:
[*.cs]
# Change from error to warning
dotnet_diagnostic.SC021.severity = warning
# Change from warning to error
dotnet_diagnostic.SC201.severity = error
# Available severity levels: none, silent, suggestion, warning, error
The analyzers use a structured diagnostic ID system with the prefix SC (StrictCSharp) followed by a three-digit number:
Rules that enforce fundamental project configuration and essential code quality standards.
000-019: Project Configuration
020-039: Documentation Requirements
040-059: Naming Conventions
Rules that enforce consistent coding style and improve code readability.
100-119: Using Statements & Resource Management
120-139: Method/Member Style
140-159: Comment Style
Rules specific to test code organization, structure, and quality.
200-219: Test Framework Requirements
220-239: Test Structure
240-259: Test Naming & Organization
Rules about analyzer configuration and rule management.