| Source | Link |
|---|---|
| VSIX | |
| VSIX(VS22) | |
| Nuget |
- Introduction
- Requirements
- Installation
- What a diagnostic looks like
- Rules
- Configuring the thresholds
- Suppressing a rule
- Changelog
- Contributing
- Repository layout
- Feedback
BlowinCleanCode is a Roslyn-based C# code analyzer that aims to provide a set of rules that helps to simplify code and make it cleaner.
It is built on top of the Roslyn compiler platform, so it analyses your code while you type and
while you build, and it works anywhere Roslyn analyzers work: Visual Studio, JetBrains Rider,
VS Code (with the C# extension) and dotnet build. Every rule is a heuristic for one of four
categories:
| Category | Prefix | Idea |
|---|---|---|
| Single responsibility | BCC2xxx |
A method or a type is doing more than one thing, or is simply too big. |
| Encapsulation | BCC1xxx |
State is exposed in a way that lets any code mutate it. |
| Good practice | BCC3xxx |
Patterns that are known to cause bugs or cost performance later. |
| Code smell | BCC4xxx |
Code that is correct but hard to read, and therefore hard to maintain. |
The analyzer ships 32 rules, all enabled by default with Warning severity, and it never
rewrites your code on its own — it only reports, and offers a code fix that suppresses the
diagnostic with a comment when you decide the rule does not apply.
This is a style and structure analyzer. It deliberately does not duplicate compiler warnings, security analyzers, or framework-specific rules.
| Distribution | Requirement |
|---|---|
| NuGet package | Any toolchain that supports Roslyn analyzers (Roslyn 3.3 and newer) |
| VSIX for Visual Studio | Visual Studio 2017 or 2019 (17.0 excluded) |
| VSIX for Visual Studio 2022 | Visual Studio 2022 (17.0 and newer, up to but excluding 18.0) |
The analyzer itself targets netstandard2.0 and is compiled against Roslyn 3.3, so it loads in
both older and current tooling.
dotnet add package Blowin.CleanCodeor add the reference manually:
<ItemGroup>
<PackageReference Include="Blowin.CleanCode" Version="2.6.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>The package is a development dependency: it is marked PrivateAssets="all" by default, so it
never flows to consumers of your library.
Install from the Visual Studio Marketplace and restart Visual Studio:
- Visual Studio 2017 / 2019 — BlowinCleanCode
Blowin.1 - Visual Studio 2022 — BlowinCleanCode
Blowin.BlowinCleanCodeVS22
The extension contains the same analyzers and the same code fix as the NuGet package.
Given this method:
public bool Validate(User user)
{
if (user.Age > 18 && user.Country == "DE" && user.IsActive && !user.IsBanned && user.Email != null)
return true;
return false;
}BlowinCleanCode reports:
warning BCC4001: The expression in the condition is too complex
warning BCC4002: Magic value '18'
Each diagnostic points at the exact expression that triggered it, and the diagnostic ID is a
stable, permanent identifier you can use in a suppression comment or in .editorconfig.
All rules are enabled by default with Warning severity. The tables below list every rule with
its diagnostic ID and the condition that triggers it. The ID block encodes the category: BCC1xxx
encapsulation, BCC2xxx single responsibility, BCC3xxx good practice, BCC4xxx code smell.
| ID | Rule | Reported when |
|---|---|---|
BCC2000 |
Cognitive complexity of the method | The cognitive complexity of a method reaches 8. The message says whether it is low, middle or high. |
BCC2001 |
Many parameters in method | A method has more than 4 parameters. |
BCC2002 |
Method contains 'And' | The name of a method contains And, as in RunAndClose — a sign that it does two things. |
BCC2003 |
Control flag | A bool parameter is used as a control flag in an if or ternary condition inside the method. |
BCC2004 |
Method contains a lot of declarations | A method has more than 10 local variable declarations. |
BCC2005 |
Too many chained references | An invocation chain is longer than 5 calls (fluent chains are counted separately). |
BCC2006 |
Large class | The weighted method count of a type exceeds 10 (private methods count 0.55, others count 1). |
BCC2007 |
Large number of fields in types | A type with methods has more than 5 non-const fields. |
BCC2008 |
Lambda have too many lines | A lambda body is longer than 10 lines. |
BCC2009 |
Long method | A method is longer than 25 lines of code (trivia and comments are not counted). |
| ID | Rule | Reported when |
|---|---|---|
BCC1000 |
Don't use public static field | A field is public and static and neither readonly nor const. |
| ID | Rule | Reported when |
|---|---|---|
BCC3000 |
Don't return null | A method with a reference return type returns null (including => null); nullable-annotated returns are ignored. Use the null object pattern instead. |
BCC3001 |
Don't use static class | A class is declared static. Consider the singleton pattern instead. |
BCC3002 |
Disposable member in non disposable class | A type holds an IDisposable/IAsyncDisposable member that it creates, but does not implement IDisposable itself. |
BCC3003 |
Switch statements should have at least 2 case clauses | A switch has fewer than 2 case clauses; an if/ternary would be more readable. |
BCC3004 |
Finalizers should not be empty | A finalizer has an empty body — it costs performance with no benefit. |
BCC3005 |
Type that provide Equals should implement IEquatable | A type declares a single-parameter Equals(T) but does not implement IEquatable<T>; Equals(object) is ignored. |
BCC3006 |
'ThreadStatic' fields should not be initialized | A field marked [ThreadStatic] has an initializer, which only initializes the first thread. |
BCC3007 |
Name is too long | An identifier is longer than 26 characters. |
BCC3008 |
Use only ASCII characters for names | An identifier contains non-ASCII characters. |
| ID | Rule | Reported when |
|---|---|---|
BCC4000 |
Nested ternary operator | A ternary operator is nested inside another one. |
BCC4001 |
Complex condition | A condition (in if, while, variable declaration, return or argument) combines more than 4 boolean terms. |
BCC4002 |
Magic value | An expression contains a literal other than 0, 1, -1 or null. String literals inside a binary expression (a comparison or a concatenation) are ignored. |
BCC4003 |
Preserve whole object | A call passes more than 2 members of the same object as separate arguments instead of the object itself. |
BCC4004 |
Hollow type name | A type name ends with Helper, Util, Utils, Utility, Utilities, Info or Data, or contains Manager. |
BCC4005 |
Deeply nested | Statements are nested more than 3 levels deep. |
BCC4006 |
Method should not have many return statements | A method has more than 4 return statements (8 when it returns bool); all returns of one switch count as 1. |
BCC4007 |
Switch should not have a lot of cases | A switch has more than 4 case clauses — polymorphism may be a better fit. |
BCC4008 |
Switch statements should not be nested | A switch statement contains another switch statement. |
BCC4009 |
Catch should do more than rethrow | A catch clause only rethrows the caught exception. |
BCC4010 |
Empty 'default' clauses should be removed | A switch has an empty default clause. |
BCC4011 |
Middle man | A type whose methods only delegate to a single field. Append Adapter to the type name to opt out. |
The rule IDs are permanent. They never change meaning, and a removed rule keeps its number reserved. New rules always take the next free number in their block.
Every rule has a threshold compiled in, and every threshold can be redefined through a standard
.editorconfig file. No extra package and no code change is required: the analyzer reads the
value from the configuration of the file it is analyzing, so an option can be set globally, per
directory, or per file.
Every option key has the form <prefix>.<diagnostic id>.<option>, so it always names the rule it
configures:
# .editorconfig, at the root of the repository
root = true
[*.cs]
# allow a few more return statements everywhere
bcc.BCC4006.max_return_statement = 6
bcc.BCC4006.max_return_statement_for_return_bool = 10
# names may be a bit longer
bcc.BCC3007.max_name_length = 30
# report cognitive complexity earlier
bcc.BCC2000.min_low_complexity = 6
# relax the rules for an old part of the code base only
[src/Legacy/**/*.cs]
bcc.BCC2009.max_count_of_lines_in_method = 60
bcc.BCC2001.max_method_parameter = 8Rules of thumb:
- The keys above are the canonical form. The lookup is case-insensitive, so
bcc.bcc4006.max_return_statementworks as well. - Use the invariant number format:
0.55, not0,55. - A value that cannot be parsed is ignored and the compiled-in default is used, so a typo never breaks the build.
- Turning a rule off, or changing its severity, is a different mechanism — see Suppressing a rule.
- Every option name is declared exactly once, in
Constant.Option; the rules for adding one are in CONTRIBUTING.md.
| Option | Default | Configures |
|---|---|---|
bcc.BCC3007.max_name_length |
26 |
BCC3007 Name is too long |
bcc.BCC2007.max_number_of_field |
5 |
BCC2007 Large number of fields |
bcc.BCC4005.max_deeply_nested |
3 |
BCC4005 Deeply nested |
bcc.BCC2004.max_method_declaration |
10 |
BCC2004 Method contains a lot of declarations |
bcc.BCC2009.max_count_of_lines_in_method |
25 |
BCC2009 Long method |
bcc.BCC2008.max_lambda_count_of_lines |
10 |
BCC2008 Lambda have too many lines |
bcc.BCC2001.max_method_parameter |
4 |
BCC2001 Many parameters in method |
bcc.BCC4001.max_count_of_condition |
4 |
BCC4001 Complex condition |
bcc.BCC4003.max_preserve_whole_object_count |
2 |
BCC4003 Preserve whole object |
bcc.BCC4006.max_return_statement |
4 |
BCC4006 Method return statements |
bcc.BCC4006.max_return_statement_for_return_bool |
8 |
BCC4006 for methods returning bool |
bcc.BCC4007.max_switch_case_count |
4 |
BCC4007 Switch should not have a lot of cases |
bcc.BCC2000.min_low_complexity |
8 |
BCC2000 Cognitive complexity |
bcc.BCC2000.min_middle_complexity |
10 |
BCC2000 Cognitive complexity |
bcc.BCC2000.min_high_complexity |
15 |
BCC2000 Cognitive complexity |
bcc.BCC2005.max_call |
5 |
BCC2005 Too many chained references |
bcc.BCC2005.max_fluent_interface_call |
not set | BCC2005 maximum length of a fluent chain |
bcc.BCC2005.must_include_fluent_interface_call |
false |
BCC2005 count fluent calls towards max_call |
bcc.BCC2006.max_method_threshold |
10 |
BCC2006 Large class |
bcc.BCC2006.private_method_threshold |
0.55 |
BCC2006 weight of a private method |
bcc.BCC2006.non_private_method_threshold |
1 |
BCC2006 weight of a non-private method |
bcc.BCC4004.full_match_words |
Helper, Util, Utils, Utility, Utilities, Info, Data |
BCC4004 Hollow type name |
bcc.BCC4004.suffix_words |
Manager |
BCC4004 words rejected as a name suffix |
The same values are exposed in code as the defaults of
AnalyzerSettings.
Changing a default is a user-visible change and follows the documentation and test rules
described in CONTRIBUTING.md.
Place a single-line comment of the form // Disable BCCxxxx on the line directly above the
statement, method or type you want to exclude:
public class OrderService
{
// Disable BCC2003
public void Process(Order order, bool validate)
{
if (validate)
order.Validate();
}
}- Above a statement — the rule stops reporting on that statement.
- Above a method — the rule stops reporting anywhere inside that method.
- Above a type — the rule stops reporting anywhere inside that type.
The comment must contain exactly // Disable followed by the diagnostic ID; anything else,
including a different comment style, is ignored. In Visual Studio the lightbulb menu offers
Disable '…' with comment for line / for method / for class, which inserts the comment for you.
Because the diagnostics are ordinary Roslyn diagnostics, the standard mechanism works as well:
[*.cs]
dotnet_diagnostic.BCC4002.severity = none # turn a rule off completely
dotnet_diagnostic.BCC2009.severity = suggestion # or lower its severityRelease notes for every version live in CHANGELOG.md.
Contributions are welcome — bug reports, false-positive reports, new rules and documentation improvements alike.
Read CONTRIBUTING.md before you start. It documents the repository structure, how diagnostic IDs and constants are allocated, how an analyzer is implemented, the mandatory test coverage, the README/changelog requirements, and the exact format of commits and pull requests.
In short:
# build and run the tests (the VSIX projects need the Visual Studio SDK and are not required)
dotnet test BlowinCleanCode/BlowinCleanCode.Test/BlowinCleanCode.Test.csprojA change is only complete when it includes tests, a README.md update (for new or changed
rules) and a CHANGELOG.md entry (for user-visible changes).
| Path | What it contains |
|---|---|
BlowinCleanCode/BlowinCleanCode/ |
The analyzer: features, base classes, constants, settings. |
BlowinCleanCode/BlowinCleanCode.CodeFix/ |
The Disable with comment code fix. |
BlowinCleanCode/BlowinCleanCode.Test/ |
The xUnit test suite, one file per rule. |
BlowinCleanCode/BlowinCleanCode.Package/ |
The Blowin.CleanCode NuGet package. |
BlowinCleanCode/BlowinCleanCode.Vsix/ |
The VSIX for Visual Studio 2017/2019. |
BlowinCleanCode/BlowinCleanCode.Analyzer.Vsix.VS22/ |
The VSIX for Visual Studio 2022. |
CHANGELOG.md |
Release notes. |
CONTRIBUTING.md |
Development rules and pull request process. |
Every rule is registered in a single place —
BlowinCleanCodeAnalyzer.cs — and
every diagnostic ID is declared in
Constant.cs.
- Found a false positive or a rule that is missing? Open an issue with a minimal code sample that reproduces it.
- Want to submit a fix? See CONTRIBUTING.md.