The dotnet format command and StyleCop.Analyzers
Tags: csharp
Published: 2022-04-03 06:57:16

This article explains how to use the dotnet format command with .editorconfig and its limitations and then shows you that StyleCop.Analyzers can overcome them. This article also mentions how to apply the StyleCop ruleset to all projects in a solution.

## About dotnet format

The dotnet format can format all source codes at once in a project or solution according to rulesets. The .NET SDK version 6.0 or later includes the dotnet format by default. If you use .NET Core 3.1, you can install it as follows.

```
dotnet tool install -g dotnet-format
```

The dotnet format reads one or more rulesets from .editorconfig files in a target project or solution folder or its ancestor folders. If you want to format your source codes to follow the [C# Coding Style](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md), you can use this [.editorconfig](https://github.com/dotnet/runtime/blob/main/.editorconfig) in the .NET Runtime repository.

When you use the dotnet format with the .editorconfig, you need to specify the info level severity. Otherwise, the dotnet format ignores most of the rules.

If <kbd>dotnet format --version</kbd> shows 6.0 or higher
```
dotnet format --severity info
```
Otherwise
```
dotnet format --fix-style info
```

The above command formats the following code to the next one.

```csharp
async private Task<string> readLineAsync(NetworkStream stream)
{
    var result = new List<byte>();
    var buffer = new byte[1];


    do {
        var n = await stream.ReadAsync(buffer);
        if ( n == 0 ) break;
        result.Add(buffer[0]);
    } while ( buffer[0] != 0x0a );

    return Encoding.UTF8.GetString(result.ToArray()).TrimEnd('\r', '\n');
        
}
```

```csharp
private async Task<string> readLineAsync(NetworkStream stream)
{
    var result = new List<byte>();
    byte[] buffer = new byte[1];


    do
    {
        int n = await stream.ReadAsync(buffer);
        if (n == 0) break;
        result.Add(buffer[0]);
    } while (buffer[0] != 0x0a);

    return Encoding.UTF8.GetString(result.ToArray()).TrimEnd('\r', '\n');

}
```

This result is not sufficient. Despite the coding style, the method name keeps camelCase,  and the `if` statement remains in a single line. There are unnecessary empty lines, too.

## StyleCop.Analyzers

To get more disciplined results, we can use [StyleCop.Analyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers). The StyleCop.Analyzers provides a way to enforce the StyleCop ruleset traditionally provided by an extension for Visual Studio.

When you format source codes in a project with StyleCop.Analyzers, you need to add the StyleCop.Analyzers package to the project as follows. Then, StyleCop.Analyzers works with the default ruleset.

```
dotnet add package Stylecop.Analyzers
```

The default ruleset doesn't follow the coding style. Therefore, you need to download and adjust [the default ruleset file](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/rulesets/StyleCopAnalyzersDefault.ruleset) from the StyleCop.Analyzers repository. You should add the following lines to the project file when you save the ruleset file as stylecop.ruleset.

```xml
<PropertyGroup>
  <CodeAnalysisRuleSet>stylecop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup></script>
```

Then, you can adjust the rules to follow the coding style in the stylecop.ruleset as follows. You can download [the adjusted file](https://github.com/fujieda/dotnet-format-settings/blob/main/stylecop.ruleset) in my repository.

```diff
--- StyleCopAnalyzersDefault.ruleset	2022-03-13 18:23:36.312971000 +0900
+++ stylecop.ruleset	2022-03-13 18:26:56.342161300 +0900
@@ -38,7 +38,7 @@
    
     <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.ReadabilityRules">
         <Rule Id="SA1100"  Action="Warning" />          <!-- Do not prefix calls with base unless local implementation exists -->
-        <Rule Id="SA1101"  Action="Warning" />          <!-- Prefix local calls with this -->
+        <Rule Id="SA1101"  Action="None" />             <!-- Prefix local calls with this -->
         <Rule Id="SA1102"  Action="Warning" />          <!-- Query clause should follow previous clause -->
         <Rule Id="SA1103"  Action="Warning" />          <!-- Query clauses should be on separate lines or all on one line -->
         <Rule Id="SA1104"  Action="Warning" />          <!-- Query clause should begin on new line when previous clause spans multiple lines -->
@@ -75,11 +75,11 @@
         <Rule Id="SA1136"  Action="Warning" />          <!-- Enum values should be on separate lines -->
         <Rule Id="SA1137"  Action="Warning" />          <!-- Elements should have the same indentation -->
         <Rule Id="SA1139"  Action="Warning" />          <!-- Use literal suffix notation instead of casting -->
-        <Rule Id="SX1101"  Action="None" />             <!-- Do not prefix local calls with 'this.' -->
+        <Rule Id="SX1101"  Action="Warning" />          <!-- Do not prefix local calls with 'this.' -->
     </Rules>
 	
     <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.OrderingRules">
-        <Rule Id="SA1200"  Action="Warning" />          <!-- Using directives should be placed correctly -->
+        <Rule Id="SA1200"  Action="None" />             <!-- Using directives should be placed correctly -->
         <Rule Id="SA1201"  Action="Warning" />          <!-- Elements should appear in the correct order -->
         <Rule Id="SA1202"  Action="Warning" />          <!-- Elements should be ordered by access -->
         <Rule Id="SA1203"  Action="Warning" />          <!-- Constants should appear before fields -->
@@ -105,16 +105,16 @@
         <Rule Id="SA1303"  Action="Warning" />          <!-- Const field names should begin with upper-case letter -->
         <Rule Id="SA1304"  Action="Warning" />          <!-- Non-private readonly fields should begin with upper-case letter -->
         <Rule Id="SA1305"  Action="None" />             <!-- Field names should not use Hungarian notation -->
-        <Rule Id="SA1306"  Action="Warning" />          <!-- Field names should begin with lower-case letter -->
+        <Rule Id="SA1306"  Action="None" />             <!-- Field names should begin with lower-case letter -->
         <Rule Id="SA1307"  Action="Warning" />          <!-- Accessible fields should begin with upper-case letter -->
         <Rule Id="SA1308"  Action="Warning" />          <!-- Variable names should not be prefixed -->
-        <Rule Id="SA1309"  Action="Warning" />          <!-- Field names should not begin with underscore -->
+        <Rule Id="SA1309"  Action="None" />             <!-- Field names should not begin with underscore -->
         <Rule Id="SA1310"  Action="Warning" />          <!-- Field names should not contain underscore -->
         <Rule Id="SA1311"  Action="Warning" />          <!-- Static readonly fields should begin with upper-case letter -->
         <Rule Id="SA1312"  Action="Warning" />          <!-- Variable names should begin with lower-case letter -->
         <Rule Id="SA1313"  Action="Warning" />          <!-- Parameter names should begin with lower-case letter -->
         <Rule Id="SA1314"  Action="Warning" />          <!-- Type parameter names should begin with T -->
-        <Rule Id="SX1309"  Action="None" />             <!-- Field names should begin with underscore -->
+        <Rule Id="SX1309"  Action="Warning" />          <!-- Field names should begin with underscore -->
         <Rule Id="SX1309S" Action="None" />             <!-- Static field names should begin with underscore -->
     </Rules>
 
@@ -140,7 +140,7 @@
         <Rule Id="SA1500"  Action="Warning" />          <!-- Braces for multi-line statements should not share line -->
         <Rule Id="SA1501"  Action="Warning" />          <!-- Statement should not be on a single line -->
         <Rule Id="SA1502"  Action="Warning" />          <!-- Element should not be on a single line -->
-        <Rule Id="SA1503"  Action="Warning" />          <!-- Braces should not be omitted -->
+        <Rule Id="SA1503"  Action="None" />             <!-- Braces should not be omitted -->
         <Rule Id="SA1504"  Action="Warning" />          <!-- All accessors should be single-line or multi-line -->
         <Rule Id="SA1505"  Action="Warning" />          <!-- Opening braces should not be followed by blank line -->
         <Rule Id="SA1506"  Action="Warning" />          <!-- Element documentation headers should not be followed by blank line -->
```

The dotnet format can format source codes with StyleCop.Analyzers. If the version of the dotnet format is 6.0 or higher, there needs nothing special to use StypeCop.Analyzers. You can use the same command shown above.

```
dotnet format --severity info
```

If the version is 5.*, you should run the command with an additional option.

```
dotnet format --fix-style info --fix-analyzers info
```

After running the above command, you can get the following result much better than the previous result.

```csharp
private async Task<string> ReadLineAsync(NetworkStream stream)
{
    var result = new List<byte>();
    byte[] buffer = new byte[1];

    do
    {
        int n = await stream.ReadAsync(buffer);
        if (n == 0)
            break;
        result.Add(buffer[0]);
    }
    while (buffer[0] != 0x0a);

    return Encoding.UTF8.GetString(result.ToArray()).TrimEnd('\r', '\n');
}
```

## Directory.Build.props

When you need to format every project in a solution folder, you need to add the StyleCop.Analyzers package to each project. There is, however, a way to eliminate this effort, the Directory.Build.props file.

When you put Directory.Build.props in the solution folder, MSBuild imports this file to the top of each project file. So you can place the following Directory.Build.props file in the solution folder to make Stylecop.Analyzers available in every project.

```xml
<Project>
  <PropertyGroup>
    <CodeAnalysisRuleSet>..\stylecop.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>
</Project>
```

## Conclusion

I illustrated how to use the dotnet format command with StyleCop.Analyzers and the Directory.Build.props file. You can format source codes in every project in a solution folder to use them and get more disciplined results than only with the .editorconfig file.