This repository demonstrates how to use Husky.NET, dotnet-format, and csharpier
Code quality is especially important in a team environment. The only way to achieve this is by enforcing quality gates. Quality gates are automated checks that the code meet quality standards.
There are two main aspects of enforcing quality gates:
Quality Attributes:
🎯 This project, demonstrates how to enforce quality gates in a .NET project using the following tools:
Assume we have initial code that does not meet the quality standards.
☝️ Note, not all checks are equal. For example, you don't really want to run the entire test suite in the pre-commit hook. You want to run only the fast checks.
I prefer csharpier, because it is fast and has a good default configuration. It is an opinionated code formatter for C#. It is similar to Prettier, and the philosophy is to have a single way to format the code.
I don't want to spend time discussing code formatting in code reviews. I want to have a single way to format the code, and I want to have it done automatically.
The only option you can argue about is the line length. The default line length is 120 characters, but you can change it to 80 characters if you prefer.
Spelling mistakes can be detected by the cspell linter
. It is a fast and configurable spell checker that runs in the terminal.
Coding style can be enforced by standard analyzers, the configuration of which can be stored in the .editorconfig
file. The analyzers can be configured to enforce the coding style, and the IDE can be configured to show warnings and errors.
Code analysis rules have various configuration options. Some of these options are specified as key-value pairs in an analyzer configuration file (.editorconfig
).
You can configure the severity level for any rule, including code quality rules and code style rules. For example, to enable a rule as a warning, add the following key-value pair to an analyzer configuration file:
[*.{cs,vb}]
# IDE0040: Accessibility modifiers required
dotnet_diagnostic.IDE0040.severity = warning
# or
# IDE0040: Accessibility modifiers required
dotnet_style_require_accessibility_modifiers = always:warning
.NET compiler platform (Roslyn) analyzers inspect your code for code quality and style issues. Starting in .NET 5, these analyzers are included with the .NET SDK and you don't need to install them separately.
While the .NET SDK includes all code analysis rules, only some of them are enabled by default. The analysis mode determines which, if any, set of rules to enable. You can choose a more aggressive analysis mode where most or all rules are enabled.
Additionally, you may consider the following analyzers:
☝️ Note, you don't have to adopt analyzers entirely. You can use only the rules that make sense for your project.
🧠 My suggestion is to work on the code quality and code style rules together with the team during the code review process. The rules should be discussed and agreed upon by the team.
There are two other options to enforce coding style:
From my experience, it can negatively impact the local build time, so I prefer to run the checks in the CI/CD pipeline.
💡 The only exception to this is to use <WarningsAsErrors>Nullable</WarningsAsErrors>
to treat nullable warnings as errors.
If you want to go further, you can use code analysis tools. The difference between code analysis tools and code analyzers is that code analysis tools are external tools that analyze your codebase and provide insights into the quality of your code.
Some of the popular tools are:
Here is an example of a SonarCloud report:
💡 You can include SonarCloud
check as part of a CI/CD pipeline. Use /d:sonar.qualitygate.wait=true
option. Otherwise, the CI/CD pipeline will not wait for the SonarCloud analysis to finish.
By default, Sonar configures pretty strict rules called "Sonar Way":
👎 The downside of using different code analysis tools is that you have to configure them separately. You have to configure the rules, the severity levels, and the exclusions. Ideally, I want to have a single configuration file (aka source of truth) that configures all the code quality checks.
Prerequisites:
dotnet tool restore
npm install -g cspell
Run it:
dotnet run husky
This command runs the following checks sequentially:
{
"$schema": "https://alirezanet.github.io/Husky.Net/schema.json",
"tasks": [
{
"name": "format",
"group": "pre-commit",
"command": "dotnet",
"args": ["csharpier", ".", "--check"]
},
{
"name": "style",
"group": "pre-commit",
"command": "dotnet",
"args": ["format", "style", ".", "--verify-no-changes"]
},
{
"name": "analyzers",
"group": "pre-commit",
"command": "dotnet",
"args": ["format", "analyzers", ".", "--verify-no-changes"]
},
{
"name": "spelling",
"group": "pre-commit",
"command": "cspell",
"args": ["lint", "**.cs", "--no-progress", "--no-summary"]
}
]
}
We can run the checks individually:
dotnet husky run --name format
dotnet husky run --name style
dotnet husky run --name analyzers
dotnet husky run --name spelling
Now, assume the developer ignores the warning and somehow commits the code and creates a pull request. The CI/CD pipeline will run the same checks, and if any of the checks fail, the pipeline will fail.
💡 I configured parallel execution for code quality gates, which helps to receive feedback faster.
Now let's fix the issues and run the checks again.
❯ dotnet husky run
[Husky] 🚀 Loading tasks ...
--------------------------------------------------
[Husky] ⚡ Preparing task 'format'
[Husky] ⌛ Executing task 'format' ...
Formatted 1 files in 952ms.
[Husky] ✔ Successfully executed in 2,307ms
--------------------------------------------------
[Husky] ⚡ Preparing task 'style'
[Husky] ⌛ Executing task 'style' ...
[Husky] ✔ Successfully executed in 15,299ms
--------------------------------------------------
[Husky] ⚡ Preparing task 'analyzers'
[Husky] ⌛ Executing task 'analyzers' ...
[Husky] ✔ Successfully executed in 12,293ms
--------------------------------------------------
[Husky] ⚡ Preparing task 'spelling'
[Husky] ⌛ Executing task 'spelling' ...
[Husky] ✔ Successfully executed in 2,802ms
--------------------------------------------------
💡 Note: The pre-commit hook takes about 30 seconds. This is already too long, so we might consider removing something from the git hook and relying only on the CI/CD.
Now, we commit the changes and push them to the repository. The CI/CD pipeline will run the checks again, and if all checks pass, the pipeline will succeed.