Creating custom FxCop rules

If you haven't done so already, you might want to consider downloading and configuring the sample rule project as described here.

Creating the project

The first step in creating a custom FxCop rule is creating a Visual Studio project for the assembly that will contain the rule (assuming, of course, that such a project doesn't already exist for hosting existing custom rules). This project should generally be a plain-vanilla library project. Since you're presumably something of a quality nut if you're bothering to create custom FxCop rules, you'll presumably want to verify that the following options are set for all configurations of your project:

If you're wondering why you would want static analysis, and hence the CODE_ANALYSIS compilation constant, enabled in even your release configurations, consider that folks really seem to like running FxCop against FxCop assemblies. Rather than wasting time explaining violations to your rule consumers, why not let your SuppressMessageAttribute justifications do the explaining for you? (If you're not using a Visual Studio edition that supports static analysis, you may wish to add the CODE_ANALYSIS compilation constant manually and use SuppressMessageAttribute to create your rule violation exclusions in order to achieve the same end result.)

Adding references to FxCop assemblies

Your rule project will need to reference at least two FxCop assemblies: FxCopSdk.dll and Microsoft.Cci.dll. Unfortunately, the versions of these assemblies differ between the stand-alone and Visual Studio-integrated editions of FxCop. If you're targeting the stand-alone edition, you should reference these assemblies from the stand-alone FxCop install directory (usually C:\Program Files\Microsoft FxCop 1.35\. If you're targeting Visual Studio static analysis, you should pull your reference from the integrated FxCop directory, which will usually be at C:\Program Files\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\. If you wish to target both editions, see the FxCop team blog for tools that will facilitate changing the FxCop assembly references.

Creating a rule base class

All your custom FxCop rules will likely subclass Microsoft.FxCop.Sdk.Introspection.BaseIntrospectionRule, which is a perfectly adequate starting point. However, you'll end up repetitively passing identical information to its constructor from each of your rule classes, so you might as well create an intermediate abstract class from which your rule implementation classes will inherit. In the Bordecal.FxCop.Rules project, that class is Bordecal.FxCop.Rules.IntrospectionRule. The only behaviour it adds to its Microsoft.FxCop.Sdk.Introspection.BaseIntrospectionRule subclass is passing of the rule documentation resource and assembly identities to the underlying constructor.

Creating a rule implementation class

Creation of a rule implementation class is, of course, the meat of the work here. The good news is that the JustifyRuleViolationSuppressions sample rule is quite trivial to implement. The bad news is that it's not quite as simple as it could be...

The base rule implementation technique is to override suitable overload(s) of the BaseIntrospectionRule.Check method in order to add code that implements your rule verification logic. In at least some cases, it's possible to take advantage of the visitor pattern implemented by FxCop plumbing to avoid writing your own code to read the details of the rule target code. Unfortunately, this doesn't work out too well for the JustifyRuleViolationSuppressions rule since use of the visitor methods results in any given attribute potentially being visited multiple times. For example, an attribute on a method ends up being visited both when its parent class is visited and when the focal method is visited. There are ways to avoid including any problems associated with a visited attribute in the problem list for an inappropriate target, but that's not terribly efficient. Instead, the sample rule avoids using the visitor methods in favour of directly examining a target object's attributes from the appropriate Check method overload.

When the rule finds a SuppressMessageAttribute without a populated Justification property, it it adds a newly constructed Microsoft.FxCop.Sdk.Problem instance to the returned Microsoft.FxCop.Sdk.Introspection.ProblemCollection. When constructing the new problem, it passes both the resolution text for the violation, as well as the source code context for the violation. The resolution text is constructed by calling the BaseIntrospectionRule.GetResolution method, which formats the resolution from the embedded rule documentation resource (more on this in a bit), substituting the GetResolution parameters for any placeholders in the resource text. To generate a text representation of a problem attribute instance, the found instance is passed to the Microsoft.FxCop.Sdk.Introspection.RuleUtilities.Format method. The source code context is passed to the problem simply by passing the attribute itself, which exposes tht source context via the SourceContext property of its Microsoft.Cci.Node base class.

Documenting a custom rule

In order for the FxCop user interface applications to be able to load your custom rule, you must provide an embedded rules resource in your assembly in the form of XML that describes your custom rule(s). Unfortunately, there seems to be no published schema for this XML file, but a bit of trial and error with FxCop.exe should quickly reveal whether you've gotten things right or not. The Rules.xml file in the sample project contains a working sample of the required XML. If you would like to see more samples, your best bet is probably to examine the various Microsoft.FxCop.Rules.____.____Rules.xml resources in the Microsoft-shipped rule assemblies via a tool like Reflector.

While providing the embedded rules resource is the minimum required documentation, you'll probably also want to make some more complete documentation available either locally or only, particularly given that the FxCop user interface displays a rather annoying "malformed URL" message if you leave the documentation Url element empty in the embedded rules resource. If you want to provide documentation that resembles the help docs published by the FxCop team (see, for example, AssembliesShouldHaveValidStrongNames), you could end up maintaining quite a bit of duplicate data: one set for your embedded rule resource, and the other for the external help.

The good news is that it's quite possible to avoid storing duplicate data for the two documentation formats, although it does require a bit of setup work. For starters, you'll need to create a single XML document that will store all the data required by both documentation sets, such as the RuleDocumentation.xml in the sample project. The next step is to dynamically generate the embedded rule resource document immediately before compilation of the project. This can be done by using an appropriate XSL transformation, such as that contained in the EmbeddedRulesResource.xslt file in the sample project. In order to run the transformation prior to compilation, simply add a pre-build event that uses msxsl.exe to invoke the transformation, as configured for the sample project.

The other side of the documentation coin is the human-consumable rule help. For ease of maintenance on lower traffic sites, I happen to favour using an ASP.NET HTTP handler that uses XSLT to transform the data from a file like RuleDocumentation.xml to something like the JustifyRuleViolationSuppressions help topic at request time. On a higher traffic site, one might prefer to either pre-generate the resulting HTML files or set up a server-side cache via other means. Either way, the general idea is to avoid manual maintenance of HTML file content by automatically generating the content from one centrally managed XML store or database. And, as long as you're going to be maintaining a centralized store, why not build your embedded rule resources from the same source?