;

Creating a custom StyleCop rule

Posted : Sunday, 31 July 2011 20:51:21

I’ve been working a short-term contract for the last month or so and one of the coding standards for the codebase is not to use implicit variable declaration. The justification for this requirement is that it makes code less readable and could be construed as lazy, I can certainly see this point of view but personally I like them, I find it quicker to write code when you cant remember exactly what the return type of a given method might or the type it self is something like IEnumerable<ICollection<Something ElseWithAReallylongName>>>. I also think it makes code more flexible to implementation changes; when you might change the return type of method to a base class for example. Regardless of my opinion, coding standards are just that and in a team environment they are essential so in this contract there is no place for var! It sounds easy, “just don’t use var” I tell myself but almost invariably, following the end-of-week code review, I get pulled up for littering the code with var. So I was looking for a solution that would enable me to automatically check my code. Another of the coding standards at this particular contract is that all code files must be StyleCop compliant- this struck me as the perfect opportunity to stamp out my use of var (for the duration of this contract at least Winking smile).

So I searched around for a bit to found out how to write custom style cop rules. These two links were really helpful:

http://scottwhite.blogspot.com/2008/11/creating-custom-stylecop-rules-in-c.html

and

http://www.planetgeek.ch/2009/07/19/custom-stylecop-rules/

The first is a really straightforward walkthrough which was child's play to follow but sadly didn’t work when I tried. The second is a two part series which was much more informative but not quite what I was looking for  - I just wanted to drop a custom rule into StyleCop and have it work. According to Scott's post, dropping my new assembly into the StyleCop installation should be all that's required.

I followed Scotts approach (altering his sample rule to a skeleton version of my own called NoVarRuleAnalyzer) but it wouldn't work for me, StyleCop just wouldn’t pick up the rule. To verify if StyleCop has loaded the rule drag the Settings.StyleCop file onto the file named  StyleCopSettingsEditor.exe and you should see something like this:

image

If the new rule has been loaded you should see it appear in whatever group you set in the Name property of the xml metadata file from Scott’s post. I followed all the steps but it just wouldn’t work, I even used reflector to poke about inside the rules assembly that ships with StyleCop to try and find any differences but I couldn't see any. I was getting a bit frustrated when I had a flash of inspiration, the two posts above date from 2008 and 2009 respectively. VisualStudio 2010 defaults to .Net4 but both of these posts pre date .Net4 – I altered the assembly version to 3.5 recompiled, dropped it in the StyleCop directory and bingo:

image

Sweet Smile.

One thing that was starting to irritate me was that in using this approach I had to close VisualStudio and delete my custom rule assembly every time I wanted to make a change to the rule. VisualStudio will load all StyleCop rule assemblies on startup so the files get locked and you have to close VisualStudio to unload them before you can overwrite them. This is where the second post listed above came to the rescue.

In the second part of the planetgeek post, the author Thomas outlines how he broke out the StyleCop execution code into a separate assembly that he uses to unit test his custom StyleCop rules. While this was a bit more effort than I was wanting to spend the validity of his approach is unquestionably sound, particularly if you intend to implement a lot of different rules. What I did take from the post however was a bit of an insight into the structure and makeup of the StyleCop application. The key class as far as executing a particular rule goes is the StyleCopConsole class. By the way I should point out that the SDK for StyleCop is available at StyleCop on CodePlex. So I added a new console application to my solution and created a new class called RuleRunner. The first thing I did was create a class level field to hold a reference to an instance of the StyleCopConsole class, in the constructor I instantiated this reference using the minimum amount of effort/parameters:

   10     public class RuleRunner
   11     {
   12         private StyleCopConsole styleCopConsole;
   13 
   14         public RuleRunner()
   15         {
   16             var settings = Path.GetFullPath("Settings.StyleCop");
   17             this.styleCopConsole = new StyleCopConsole(settings, false, null, null, true);
   18 
   19             this.styleCopConsole.ViolationEncountered += ((sender, args) => WriteViolation(args.Violation));
   20             this.styleCopConsole.OutputGenerated += ((sender, args) => WriteOutput(args.Output));
   21         }

As you can see I also hooked up a couple of event handlers to capture output from the StyleCopConsole instance. In order to run, StyleCop needs a settings file – I simply copied the one that was created upon installation, added it to my console project and set the build action to copy. I then added a method to this class called RunRules taking a string parameter representing the code file to be analysed. This constructor and method form the public interface of my RuleRunner class. I then added a few private methods to the class, one to load the specified code file into the code project, another to run the analysis, a third to output any messages to the console window and a fourth and final method to summarise any rule violations into a readable summary of the violation. I’ve shown the RunRules method but have omitted the others – its not rocket science what they do and the code will be available to download at the end of this post.

   23         public void RunRules(string codeFilename)
   24         {
   25             //initialise codeproject with minimum required params
   26             CodeProject codeProject = new CodeProject(Guid.NewGuid().GetHashCode(), null, new Configuration(new string[0]));
   27 
   28             //add specified codefile to project
   29             AddCodeFile(codeProject, codeFilename);
   30 
   31             //analyse
   32             StartAnalysis(codeProject);
   33         }

I then added some code to the Console's program class to instantiate and then invoke my RunRules method:

   10     class Program
   11     {
   12         static void Main(string[] args)
   13         {
   14             RuleRunner ruleRunner = new RuleRunner();
   15             ruleRunner.RunRules(@"DummyClass.cs");
   16 
   17             System.Console.WriteLine("press any key to exit...");
   18             System.Console.ReadKey();
   19         }
   20     }
 

As you can see I refer to a code file called DummyClass.cs – this is a trivial class that just breaks my new rule with a couple of uses of implicit variable declaration – effectively a unit test. In order to load it as a source file I changed the build action to None and copy to output directory. That was my console application complete – you could refactor the Main method to take the code file name as a parameter but I’ll leave that as an exercise for the reader. One thing I did need to do was add a reference to the NoVarRuleAnalyzer class I created earlier – this ensures that it will be copied to the Console applications bin directory and then loaded (via reflection) by the StyleCopConsole class.

So now to the implementation of the NoVarRuleAnalyzer class. If you’ve not done so already, look at Scott's post for an explanation of what is required. I’ll go straight into how I implemented my “no var” rule:

   10     [SourceAnalyzer(typeof(CsParser))]
   11     public class NoVarRuleAnalyzer : SourceAnalyzer
   12     {
   13         public override void AnalyzeDocument(CodeDocument document)
   14         {
   15             CsDocument csdocument = (CsDocument)document;
   16             if (csdocument.RootElement != null && !csdocument.RootElement.Generated)
   17                 csdocument.WalkDocument(new CodeWalkerElementVisitor<object>(this.VisitElement), null, null);
   18         }

Line 16 checks that code exists in the file and that it is not auto-generated, if these criteria are met then WalkDocument is called. For every element encountered in the code file, the callback method listed below will be invoked:

   20         private bool VisitElement(CsElement element, CsElement parentElement, object context)
   21         {
   22             // Flag a violation if "var" appears
   23             if (!element.Generated && 
   24                 (element.ElementType == ElementType.Method 
   25                 || element.ElementType == ElementType.Constructor 
   26                 || element.ElementType == ElementType.Property))
   27             {
   28                 foreach (var token in element.Tokens)
   29                 {
   30                     if (token.Text == "var")
   31                     {
   32                         AddViolation(element, token.LineNumber, "NoVarRuleAnalyzer");
   33                     }
   34                 }
   35             }
   36             return true;
   37         }

If the encountered element is either a constructor, a method or a property ( did I miss anywhere else that var could be used?), each token in that element is checked, if any are “var” then a violation is added using the line the token appears on. This is then fed back via the StyleCop output so the user can double click the violation and be taken straight to the offending line. I ran the code and got this:

image

Dropping the assembly containing the NoVarRuleAnalyzer  class into the StyleCop installation directory and then running StyleCop through VisualStudio gave this output:

image

Nice. My code-reviews from now on should at least have fewer issues Winking smile. I’ll keep fighting the corner for implicit variable declaration but at least I’ll be able to adhere to coding standards while the debate goes on. I hope this will be of use to someone – drop me a line if it is or you have any suggestions on how to improve it.

  • (This will not appear on the site)