Monday, May 20, 2013

Distributing custom stylecop rules in a scrum team


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))]
public class MyCodeStandardAnalyzerRules : SourceAnalyzer
{

    public override void AnalyzeDocument(CodeDocument document)
    {
        var csDoc = document as CsDocument;
        if(csDoc == null) return;
        if(csDoc.HasEmptyRootElement() || csDoc.IsAutoGeneratedCode()) return;

        csDoc.WalkDocument(
                ElementVisitor, null, null
            );

    }

    private bool ElementVisitor(CsElement element, CsElement parentelement, object context)
    {
        //Implementing the custom rules for CsElement goes here
    }
}

Creating the visitor interface
public interface IVisitor
{
    void SetSourceAnalyzer(SourceAnalyzer alanyzer);
    void Visit(CsElement element);
}

Writing the first visitor
public class CheckForConstantsHaveUpperCaseNameVisitor : BaseVisitor, IVisitor
{
    public void 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);
    }
}
public class BaseVisitor
{
    private SourceAnalyzer _sourceAnalyzer;

    public void SetSourceAnalyzer(SourceAnalyzer sourceAnalyzer)
    {
        if (sourceAnalyzer == null) throw new ArgumentNullException("sourceAnalyzer");
        _sourceAnalyzer = sourceAnalyzer;
    }

    protected void AddViolation(ICodeElement element, string rule, params object[] values)
    {
        _sourceAnalyzer.AddViolation(element, rule, values);
    }
}

Creating the Element class to accept the visitor
public class CodeElement
{
    private readonly SourceAnalyzer _analyzer;
    private readonly CsElement _element;

    public CodeElement(SourceAnalyzer analyzer, CsElement element)
    {
        _analyzer = analyzer;
        _element = element;
    }

    public void 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)
public class VisitorDispatcher
{
    private static VisitorDispatcher _dispatcher;
    private static readonly object _lockObject = new object();

    public List<IVisitor> Visitors { get; private set; }

    public static VisitorDispatcher Instance
    {
        get
        {
            if (_dispatcher == null)
            {
                lock (_lockObject)
                {
                    _dispatcher = new VisitorDispatcher();
                    var visitors = from type in Assembly.GetExecutingAssembly().GetTypes()
                                    where type.IsAssignableFrom(typeof(IVisitor))
                                    select Activator.CreateInstance(type) as IVisitor;

                    _dispatcher.Visitors = new List<IVisitor>(visitors);

                }
            }
            return _dispatcher;
        }
    }
}

Finally visiting the element with the visitors
private bool ElementVisitor(CsElement element, CsElement parentelement, object context)
{
    if (element.IsAutoGenerated()) return true;

    var codeElement = new CodeElement(this, element);
    VisitorDispatcher.Instance.Visitors.ForEach(codeElement.Accept);
    return true;
}

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.

No comments: