The Microsoft source analyzer for C#, StyleCop can be used to enforce a set of styling and consistency rules among .Net teams in a project/ company. StyleCop can be run as a visual studio plugin or can be integrated with an MSBuild project. StyleCop provides an extensible framework for plugging in custom rules for the developers. For implementing a custom rule, the user needs to create a custom rules analyzer class which inherits the SourceAnalyzer and overrides the AnalyzeDocument method to check for violations.
In this post, I’ll show how you can implement extensible custom rules in stylecop by using the visitor pattern.
Creating the custom rule analyzer class:
[SourceAnalyzer(typeof(CsParser))]
publicclassMyCodeStandardAnalyzerRules : SourceAnalyzer
{
publicoverridevoid AnalyzeDocument(CodeDocument document)
{
var csDoc = document asCsDocument;
if(csDoc == null) return;
if(csDoc.HasEmptyRootElement() ||
csDoc.IsAutoGeneratedCode()) return;
csDoc.WalkDocument(
ElementVisitor, null, null
);
}
privatebool ElementVisitor(CsElement element, CsElement parentelement, object context)
{
//Implementing the custom rules for CsElement goes here
}
}
Creating the visitor interface
publicinterfaceIVisitor
{
void SetSourceAnalyzer(SourceAnalyzer alanyzer);
void Visit(CsElement element);
}
Writing the first visitor
publicclassCheckForConstantsHaveUpperCaseNameVisitor : BaseVisitor, IVisitor
{
publicvoid Visit(CsElement element)
{
if (element.ElementType != ElementType.Field || !element.Declaration.ContainsModifier(CsTokenType.Const))
return;
if(element.Name.Any(char.IsLower))
AddViolation(element, RuleNames.CONSTANTS_SHOULD_BE_IN_UPPERCASE,
element.Name, element.LineNumber);
}
}
publicclassBaseVisitor
{
privateSourceAnalyzer _sourceAnalyzer;
publicvoid SetSourceAnalyzer(SourceAnalyzer sourceAnalyzer)
{
if (sourceAnalyzer == null) thrownewArgumentNullException(“sourceAnalyzer”);
_sourceAnalyzer = sourceAnalyzer;
}
protectedvoid AddViolation(ICodeElement element, string rule, paramsobject[] values)
{
_sourceAnalyzer.AddViolation(element, rule, values);
}
}
Creating the Element class to accept the visitor
publicclassCodeElement
{
privatereadonlySourceAnalyzer _analyzer;
privatereadonlyCsElement _element;
public CodeElement(SourceAnalyzer analyzer, CsElement element)
{
_analyzer = analyzer;
_element = element;
}
publicvoid Accept(IVisitor visitor)
{
visitor.SetSourceAnalyzer(_analyzer);
visitor.Visit(_element);
}
}
The visitor dispatcher to resolve all visitors (Using a dependency injection container is much easier J)
publicclassVisitorDispatcher
{
privatestaticVisitorDispatcher _dispatcher;
privatestaticreadonlyobject _lockObject = newobject();
publicList<IVisitor> Visitors { get; privateset; }
publicstaticVisitorDispatcher Instance
{
get
{
if (_dispatcher == null)
{
lock (_lockObject)
{
_dispatcher = newVisitorDispatcher();
var visitors = from type inAssembly.GetExecutingAssembly().GetTypes()
where type.IsAssignableFrom(typeof(IVisitor))
selectActivator.CreateInstance(type)
asIVisitor;
_dispatcher.Visitors = newList<IVisitor>(visitors);
}
}
return _dispatcher;
}
}
}
Finally visiting the element with the visitors
privatebool ElementVisitor(CsElement element, CsElement parentelement, object context)
{
if (element.IsAutoGenerated()) returntrue;
var codeElement = newCodeElement(this, element);
VisitorDispatcher.Instance.Visitors.ForEach(codeElement.Accept);
returntrue;
}
The last step is to build your project and drop the new project’s dll into the StyleCop installation directory and run the style cop
rules on your projects. Style cop will automatically look for assemblies in the directory and pick up the new rules for your team.