Auto Fix StyleCop Warnings with Free Extensions

20 minute read

If you are like me, you like the idea of StyleCop. It helps ensure your code is consistently organized and your diffs are smaller. But the fact is, with standard Visual Studio you end up having to do many fixes by hand. Or, you have to run the auto fix for each warning type individually. This becomes a big problem if you also want to enforce StyleCop warnings (with gated code check-in or a step in your CI build) and want your team to stay happy. Thankfully, there are free extensions which can eliminate most of this pain.

The two extensions I took a look at were:

  • Code Maid - This is one of the most popular extensions in the Visual Studio Marketplace, and has a wide range of functionality.
  • Code Formatter - Is an alternative which is more tightly focused on fixing StyleCop issues.

Stress Test

The first big question with tools like this is what they can actually fix. To that end I prepared a file with as many different StyleCop warnings as I could. Not all warnings were mutually compatible, but the resulting file should be enough for our purposes. That file is all the way at the bottom of the post. I originally intended to to organize the warnings by type in the file and ended up giving that up. The file is just a disaster… but that was the point.

If you are interested in playing with the file, the only special consideration is the UnsafeMethod method. If you don’t normally write unsafe code, you can go ahead and remove that method.

For this test all StyleCop rules were left enabled (I normally disable a few). I also made a few changes to the settings of each tool to try to bring their behavior into alignment.

Stress Test Results

This table contains one row for every StyleCop warning produced by the stress test file. The columns for each tool indicate whether they fixed some or all occurrences of an issue. I should note that most errors only occurred only once in the file. I included ‘some’ because a warning like SA1009 can usually be corrected automatically by both tools. However, in generating some of the other warnings I included unusual cases which these tools couldn’t handle.

This isn’t a perfect test, so the best way to read these results is not as an exact description of the capabilities, but as a general representation of what they can accomplish. In this respect the tools are quite comparable. Indeed, what they can and can’t fix makes quite a lot of sense.

  • Both do a very good job handling whitespace.
  • They can’t add or change text, so they never fix things like variable or type names and they won’t prefix local calls with this..
  • They generally don’t move text between lines.
  • They don’t reorder or change your code within a method or statement. So changing Nullable<int> to int? or readonly internal to internal readonly will not happen. To be clear, they will make changes like reordering methods so public come before private.
Warning Detail Code Formatter Code Maid
SA0001 XML comment analysis is disabled due to project configuration    
SA1001 Commas should be followed by whitespace. Some Some
SA1002 Semicolons should not be preceded by a space. Some All
SA1003 Operator ‘+’ should be preceded by whitespace. All All
SA1004 Documentation line should begin with a space.    
SA1005 Single line comment should begin with a space.    
SA1006 Preprocessor keyword ‘if’ should not be preceded by a space. All  
SA1007 Operator keyword should be followed by a space. All All
SA1008 Opening parenthesis should not be followed by a space. All All
SA1009 Closing parenthesis should not be followed by a space. Some Some
SA1010 Opening square brackets should not be preceded by a space. All All
SA1011 Closing square bracket should not be preceded by a space. All All
SA1012 Opening brace should be preceded by a space. All All
SA1013 Closing brace should be preceded by a space. All All
SA1014 Opening generic brackets should not be followed by a space. All All
SA1015 Closing generic bracket should be followed by a space. All All
SA1016 Opening attribute brackets should not be followed by a space. All All
SA1017 Closing attribute brackets should not be preceded by a space. All All
SA1018 Nullable type symbol should not be preceded by a space. All All
SA1019 Member access symbol ‘.’ should not be preceded by a space. All All
SA1020 Increment symbol ‘++’ should not be preceded by a space. All All
SA1021 Negative sign should not be followed by a space. All All
SA1022 Positive sign should not be followed by a space. All All
SA1023 Dereference symbol ‘*’ should not be preceded by a space. All All
SA1024 Colon should be preceded by a space. All All
SA1025 Code should not contain multiple whitespace characters in a row. All All
SA1026 The keyword ‘new’ should not be followed by a space or a blank line. All All
SA1027 Tabs and spaces should be used correctly   All
SA1028 Code should not contain trailing whitespace Some All
SA1100 Do not prefix calls with base unless local implementation exists    
SA1101 Prefix local calls with this    
SA1102 Query clause should follow previous clause.    
SA1103 Query clauses should be on separate lines or all on one line All All
SA1104 Query clause should begin on new line when previous clause spans multiple lines All All
SA1105 Query clauses spanning multiple lines should begin on own line All All
SA1106 Code should not contain empty statements    
SA1107 Code should not contain multiple statements on one line    
SA1108 Block statements should not contain embedded comments    
SA1110 Opening parenthesis or bracket should be on declaration line.    
SA1111 Closing parenthesis should be on line of last parameter    
SA1112 Closing parenthesis should be on line of opening parenthesis    
SA1113 Comma should be on the same line as previous parameter.    
SA1114 Parameter list should follow declaration    
SA1115 The parameter should begin on the line after the previous parameter.    
SA1116 The parameters should begin on the line after the declaration, whenever the parameter span across multiple lines    
SA1117 The parameters should all be placed on the same line or each parameter should be placed on its own line.    
SA1118 The parameter spans multiple lines    
SA1119 Statement should not use unnecessary parenthesis    
SA1120 Comments should contain text   All
SA1121 Use built-in type alias All  
SA1122 Use string.Empty for empty strings    
SA1123 Region should not be located within a code element. All  
SA1124 Do not use regions All  
SA1125 Use shorthand for nullable types    
SA1127 Generic type constraints should be on their own line    
SA1128 Put constructor initializers on their own line    
SA1129 Do not use default value type constructor    
SA1130 Use lambda syntax    
SA1131 Constant values should appear on the right-hand side of comparisons    
SA1132 Each field should be declared on its own line    
SA1133 Each attribute should be placed in its own set of square brackets.    
SA1134 Each attribute should be placed on its own line of code. All All
SA1135 Using directive for namespace ‘System.IO’ should be qualified All All
SA1136 Enum values should be on separate lines    
SA1137 Elements should have the same indentation    
SA1139 Use literal suffix notation instead of casting    
SA1200 Using directive should appear within a namespace declaration Some Some
SA1201 A field should not follow a enum    
SA1202 ‘internal’ members should come before ‘private’ members    
SA1204 Static members should appear before non-static members Some  
SA1205 Partial elements should declare an access modifier All  
SA1206 The ‘internal’ modifier should appear before ‘readonly’    
SA1207 The keyword ‘protected’ should come before ‘internal’.   All
SA1208 Using directive for ‘System.Linq’ should appear before directive for ‘System.Runtime.CompilerServices’ All All
SA1209 Using alias directives should be placed after all using namespace directives. All All
SA1210 Using directives should be ordered alphabetically by the namespaces. All  
SA1211 Using alias directive for ‘C’ should appear before using alias directive for ‘T’ All All
SA1212 A get accessor appears after a set accessor within a property or indexer.    
SA1214 Readonly fields should appear before non-readonly fields    
SA1216 Using static directives should be placed at the correct location. All All
SA1217 The using static directive for ‘System.Math’ should appear after the using static directive for ‘System.Console’ All All
SA1300 Element ‘number1’ should begin with an uppercase letter    
SA1302 Interface names should begin with I    
SA1303 Const field names should begin with upper-case letter.    
SA1306 Field ‘Field1’ should begin with lower-case letter    
SA1307 Field ‘errors’ should begin with upper-case letter    
SA1308 Field ‘s_Name’ should not begin with the prefix ‘s_’    
SA1309 Field ‘_errors3’ should not begin with an underscore    
SA1311 Static readonly fields should begin with upper-case letter    
SA1312 Variable ‘Str’ should begin with lower-case letter    
SA1314 Type parameter names should begin with T    
SA1400 Element ‘foo’ should declare an access modifier All All
SA1401 Field should be private    
SA1403 File may only contain a single namespace    
SA1405 Debug.Assert should provide message text    
SA1407 Arithmetic expressions should declare precedence    
SA1408 Conditional expressions should declare precedence    
SA1411 Attribute constructor should not use unnecessary parenthesis    
SA1413 Use trailing comma in multi-line initializers    
SA1500 Braces for multi-line statements should not share line    
SA1501 Statement should not be on a single line    
SA1502 Element should not be on a single line Some  
SA1505 An opening brace should not be followed by a blank line. Some All
SA1506 Element documentation headers should not be followed by blank line All  
SA1507 Code should not contain multiple blank lines in a row Some All
SA1508 A closing brace should not be preceded by a blank line. Some All
SA1509 Opening braces should not be preceded by blank line.    
SA1510 ‘catch’ statement should not be preceded by a blank line   All
SA1512 Single-line comments should not be followed by blank line    
SA1514 Element documentation header should be preceded by blank line All All
SA1515 Single-line comment should be preceded by blank line    
SA1516 Elements should be separated by blank line All All
SA1517 Code should not contain blank lines at start of file All All
SA1519 Braces should not be omitted from multi-line child statement    
SA1520 Use braces consistently    
SA1600 Elements should be documented    
SA1601 Partial elements should be documented    
SA1602 Enumeration items should be documented    
SA1604 Element documentation should have summary    
SA1606 Element documentation should have summary text    
SA1607 Partial element documentation should have summary text    
SA1611 The documentation for parameter ‘value’ is missing    
SA1615 Element return value should be documented    
SA1626 Single-line comments should not use documentation style slashes All  
SA1629 Documentation text should end with a period    
SA1633 The file header is missing or not located at the top of the file.    
SA1649 File name should match first type name.    

Recommendation

In practice, either of these tools should handle the vast majority of StyleCop warnings that you generate on a day to day basis. So, I highly recommend using using one of these tools. Between the two, I would recommend Code Maid for a few reasons:

  • Code Maid has a wider set of configurations options, so you should be able to have it closely meet your team’s needs.
  • Code Maid can export a file with those configurations. You can then share that file across your team so everyone’s code clean-up is done the same way.
  • Code Maid can clean up comments so that each line is a consistent length. I have found this to be a big time savings, and I am more likely to edit a comment I see that’s inaccurate or incomplete, because I don’t have to spend time fiddling with line lengths.
  • Code Maid can be set to automatically do all of this when files are saved. There is clearly a hit to performance (which Visual Studio complains about on my machine), but I have enjoyed the extra bit of automation, compared to manually kicking off a cleanup. If performance is a concern, check out Code Formatter, in my testing it felt quicker.

Stress Test File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using T = System.Text ;
using C = System.Runtime.CompilerServices;
using System.Linq;

using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using static System.Math;
using static System.Console;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace System.Threading
{
    using IO;
    using Tasks;
}

namespace StyleCopAutoFixStressTest
{


    public class StressTest:IDisposable
    {
        public enum TestEnum { A,B}
        private int Field1,
    field2; // SA1132
        internal int errors;
        readonly internal int _errors3;
        internal protected int errors2;
        protected int count;
        const string foo = "foo";
        int nNumber;

        private string s_Name = "sdf";
        static readonly string name = "sdf";

        public readonly int errors5 = 5;
        public interface badInterface { }


        [ Required(), MaxLength(5) ][MinLength(2)]
        public int ?  NullableInt { get; set; }

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public int number1 { set; get; }
        internal int Number2 { get; set; }
        private int Number3 { get; set; }

        partial class SubClass<Param>{}
        static public void  StaticVoid() => throw new NotImplementedException();
        /// <summary>
        ///Docs
        /// </summary>
        public void Spacing()
        {
            //single line comment should begin with space.

            var a = ( 1+2 )+2;
            a ++;
            var b = - 5;
            var c = + 5;
            var value = false;
            var list = new List< string > { "sdf" , "jsf" };
            list . Add("string");

            if (!value)
            {
            }

            var array = new int [ 0 ] ;

            if (true){var z = true;}

            // Tab is here -> 	 

            var d = new [] { 1, 10, 100, 1000 };

            base.GetType();
            var e = count;
        }

        static unsafe public void UnsafeMethod(int p, out int id)
        {
            int* i = (int * )(p);
            id = *i++;
        }

        public void Readability()
        {
            IList<string> stringList = new List<string>() {
    "C# Tutorials",
    "VB.NET Tutorials",
    "Learn C++"
    , "MVC Tutorials" ,
    "Java"
};

            var result = from g in stringList

                         where g.Contains("Tutorials")

                         select g;

            var result2 = from h in stringList where h.Contains("Tutorials")
select h;

            var elementNames =
    from element in GetElements(
        12,
                    45
    ) select TransformElement
    (
        element); ;


            if (true)
            #region
            // Make sure x does not equal y
            {
            }
            #endregion

                #region
            string GetName(
                )
            {
                return "";
            }
                #endregion

            void JoinName(

    string first, 
    
    string last)
            {
            }

            JoinName(
                "A",
                "B" +
                "c");

            //
            String s = "s";

            void ReJoinName(string first, string middle,
string last)
            {
                
            }

            IEnumerable<string> GetElements(int a
                , int b) => throw new NotImplementedException();
            string TransformElement(string s) => throw new NotImplementedException();

            Nullable<int> nullable = 5;
            DoSomething(); //Note, SA1126 is disabled by default

            void Method<T, R>() where T : class where R : class, new()
            {
            }

            ImmutableArray<int> array = new ImmutableArray<int>();
            Action a = delegate { var x = 0; };
            var x = (long)1;
            ValueTuple<int, int> t1; // SA1141
            (int valueA, int valueB) t2;

            var y = t2.Item1; // SA1142
        }

        private void Naming()
        {
            var s_str = "b";
            var Str = "b";
            (int ValueA, int ValueB) T2;
        }

        public (int ValueA, int ValueB) ExampleMethod() => throw new NotImplementedException();
        /// <summary>
        /// 
        /// </summary>
        internal void MaintainabilityRules()
        {
            var a = (1);
            int x = 5 + 4 * 3 / 6 % 4 - 2;

            if (true || false && false && true || true)
            {
            }

            Debug.Assert(!true);
            Debug.Fail(string.Empty) ;

            try
            {
            }
            catch (Exception ex)
            {
            }

            
        }

        /// <summary>
        /// Documentation for the first part of Class1.
        /// </summary>
        public partial class Class1
        {
        }

        /// <summary>
        /// </summary>
        public partial class Class1
        {
        }

        /// <param name="err">fsdf</param>

        public static bool Layout(bool err)
        {
            if (err) throw new Exception();
            else
            {
                var a = 1;
            }

            if (err)

            {

                throw new Exception();
            }

            try
            {
                
            }

            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            while (err)

            {

            }

            if (true)
                return
                    err;
        }

        /// <return>
        /// returns void;
        /// </return>
        public void Method(string value)
        {
            if (null == value) // SA1131
            {
                throw new ArgumentNullException(nameof(value));
            }

            /// tripple slash comment
        }

        public class TypeName
        {
            public TypeName() : this(0)
            {
            }

            public TypeName(int value)
            {
            }
        }

        #region 
        private void DoSomething()
        {
        }
        #endregion

        public static StressTest operator +(StressTest a, StressTest b) => throw new NotImplementedException();

    }

# if Debug
#endif
}

Updated: