Sunday, September 6, 2009

Customizing rules using StyleCop 4.3

I searched on many sites for “How to write our own customized rules using StyleCop 4.3” but didn’t get fruitful information, so thought of writing one which gives you comfortable information about writing your own rules. I have explained few and also added Code snippets.

We are going to cover following rules in coming examples.

  • String Variable should start with ‘s’ and should follow Camel Casing.
  • Boolean Variable should start with 'b' and should follow Camel Casing.
  • Constant should start with 'CONST_'.
  • Object should start with 'obj' and should follow Camel Casing.
  • Variable Declaration within Loop should be avoided.
  • Enum should require Default Value.

First the most important thing that one should know is constructing your basic foundation by creating C# Class project.

Note: - Please visit following URL for clearing your basic doubts about StyleCop and How to create Basic Foundation project. We are not going to cover those things in this article.

http://www.codeproject.com/KB/cs/StyleCop.aspx

StyleCop assembly provides us different ElementTypes such as

  • ElementType.Namespace
  • ElementType.Class
  • ElementType.Interface
  • ElementType.Constructor

ElementType is defined as a separate code unit, so the code which falls under specific code unit gets executed when we run StyleCop on any cs file.

e.g. if we write code under NameSpace element type then we will be able to validate the NameSpace declaration. In below example we are checking whether namespace declared / have qualified name or not.

if (element.ElementType == ElementType.Namespace)

{

if (element.Declaration.Name.Split('.').Length < 2)

this.AddViolation(parentElement, "NameSpaceRule", "EnterFullyQualifiedName");

return true;

}

*"NameSpaceRule" should be defined in XML file.

So let’s take examples one by one.

1) String Variable should start with ‘s’ and should follow Camel Casing.

2) Boolean Variable should start with 'b' and should follow Camel Casing.

3) Object should start with 'obj' and should follow Camel Casing.

Following is the code which falls under Method Element Type (i.e. ElementType.Method)

if (element.ElementType == ElementType.Method)

{

IEnumerator iEnumerator = element.Variables.GetEnumerator();

string strType = string.Empty;

string strName = string.Empty;

bool bValueType = false;

int iLineNumber = 0;

while (iEnumerator.MoveNext())

{

strType = ((Variable)iEnumerator.Current).Type.ToString();

strName = ((Variable)iEnumerator.Current).Name;

iLineNumber = ((Variable)iEnumerator.Current).Location.LineNumber;

ValidateVariableDeclaration(strType, strName, iLineNumber, parentElement);

bValueType = ((Variable)iEnumerator.Current).GetType().IsValueType;

if (bValueType == false && !arrValueTypes.Contains(strType))

ValidateVariableDeclaration("Object",strName,iLineNumber,parentElement);

}

}

ValidateVariableDeclaration method will validates the rule

private void ValidateVariableDeclaration(string strType, string strName, int iLineNumber, CsElement parentElement)

{

switch (strType)

{

case "string":

if (strName.Length < 2 || strName.Substring(0, 1) != "s" || (strName.Substring(1, 1).ToUpper()) != strName.Substring(1, 1))

this.AddViolation(parentElement, iLineNumber, "StringVariableRule", "StringVariableShouldStartWith_s");

break;

case "boolean":

if (strName.Length < 2 || strName.Substring(0, 1) != "b" || (strName.Substring(1, 1).ToUpper()) != strName.Substring(1, 1))

this.AddViolation(parentElement, iLineNumber, "BooleanVariableRule", "BooleanVariableShouldStartWith_b");

break;

case "Object":

ValidateObjectVariableDeclaration(strName, iLineNumber, parentElement);

break;

default:

break;

}

}

Declaring ValidateObjectVariableDeclaration() method

private void ValidateObjectVariableDeclaration(string strName, int iLineNumber, CsElement parentElement)

{

if (strName.Length < 3 || strName.Substring(0, 3) != "obj" || (strName.Substring(3, 1).ToUpper()) != strName.Substring(3, 1))

this.AddViolation(parentElement, iLineNumber, "ObjectVariableRule", "ObjectVariableShouldStartWith_Obj");

}

You can customized the rule by altering if condition as per your requirements.

4) Constant should start with 'CONST_'.

Similar to the Variable declaration rule this rule also comes under Method Element type

if (element.ElementType == ElementType.Method)

{

IEnumerator iEnumerator = element.Variables.GetEnumerator();

string strType = string.Empty;

string strName = string.Empty;

bool bValueType = false;

int iLineNumber = 0;

while (iEnumerator.MoveNext())

{

strType = ((Variable)iEnumerator.Current).Type.ToString();

strName = ((Variable)iEnumerator.Current).Name;

iLineNumber = ((Variable)iEnumerator.Current).Location.LineNumber;

if (((Variable)iEnumerator.Current).Modifiers.ToString() == "Const")

ValidateConstRule(strName, iLineNumber, parentElement);

}

}

Now we will write ValidateConstRule which contains the conditions for Validating the Constant Variable.

private void ValidateConstRule(string strName, int iLineNumber, CsElement parentElement)

{

if (strName.Length < 7 || strName.Substring(0, 6) != "CONST_")

this.AddViolation(parentElement, iLineNumber, "ConstVariableRule", "ConstantShouldStartWith_CONST_");

}

5) Variable Declaration within Loop should be avoided.

Similar to Variable declaration rule this rule comes under Method Element type.

if (element.ElementType == ElementType.Method)

{

#region Try-Catch-Finally Validation & Declaration of Variable in Loop Validation

Boolean bTryStatement = false;

IEnumerator iEnum = element.ChildStatements.GetEnumerator();

switch (((Statement)(iEnum.Current)).StatementType.ToString())

{

case "While":

if (((WhileStatement)iEnum.Current).EmbeddedStatement.Variables.Count > 0)

{

this.AddViolation(parentElement, "WhileLoopRule", "WhileLoopShouldNotContainVariableDeclaration");

}

break;

case "For":

if (((ForStatement)iEnum.Current).EmbeddedStatement.Variables.Count > 0)

{

this.AddViolation(parentElement, "ForLoopRule", "ForLoopShouldNotContainVariableDeclaration");

}

break;

case "ForEach":

if (((ForeachStatement)iEnum.Current).EmbeddedStatement.Variables.Count > 0)

{

this.AddViolation(parentElement, "ForEachLoopRule", "ForEachLoopShouldNotContainVariableDeclaration");

}

break;

case "DoWhile":

if (((DoWhileStatement)iEnum.Current).EmbeddedStatement.Variables.Count > 0)

{

this.AddViolation(parentElement, "DoWhileLoopRule", "DoWhileLoopShouldNotContainVariableDeclaration");

}

break;

default:

break;

}

6) Enum should require Default Value.

StyleCop provides a separate ElementType for Enumeration section. We are going to use the same for validating Enumerations.

if (element.ElementType == ElementType.Enum)

{

bool bDefaultEnumValue = false;

IEnumerator iEnums = element.ChildElements.GetEnumerator();

while (iEnums.MoveNext())

{

if (((EnumItem)(iEnums.Current)).Initialization != null)

{

bDefaultEnumValue = true;

}

}

if (bDefaultEnumValue == false)

this.AddViolation(parentElement,element.LineNumber, "EnumDefaultRule", "EnumShouldHaveDefaultValue");

}

Now in following section we are going to create Rules XML file where the entire Rule’s Context as well as Description will be declared.

For all the above rules you need to add following Nodes under Specific Rule Group

  • String Variable should start with ‘s’ and should follow Camel Casing.

<Rule Name="StringVariableRule" CheckId="HE3336">

<Context>String Variable should start with 's'.</Context>

<Description>String Variable should start with 's'.</Description>

</Rule>

  • Boolean Variable should start with 'b' and should follow Camel Casing.

<Rule Name="BooleanVariableRule" CheckId="HE3338">

<Context>Boolean Variable should start with 'b'.</Context>

<Description>Boolean Variable should start with 'b'.</Description>

</Rule>

  • Constant should start with 'CONST_'.

<Rule Name="ConstVariableRule" CheckId="HE3342">

<Context>Constant should start with 'CONST_'.</Context>

<Description>Constant should start with 'CONST_'.</Description>

</Rule>

  • Object should start with 'obj' and should follow Camel Casing.

<Rule Name="ObjectVariableRule" CheckId="HE3343">

<Context>Object should start with 'obj'.</Context>

<Description>Object should start with 'obj'.</Description>

</Rule>

  • Variable Declaration within Loop should be avoided.

<Rule Name="WhileLoopRule" CheckId="HE3344">

<Context>While Loop Should Not Contain Variable Declaration.</Context>

<Description>While Loop Should Not Contain Variable Declaration.</Description>

</Rule>

<Rule Name="ForLoopRule" CheckId="HE3345">

<Context>For Loop Should Not Contain Variable Declaration.</Context>

<Description>For Loop Should Not Contain Variable Declaration.</Description>

</Rule>

<Rule Name="ForEachLoopRule" CheckId="HE3344">

<Context>For Each Loop Should Not Contain Variable Declaration.</Context>

<Description>For Each Loop Should Not Contain Variable Declaration.</Description>

</Rule>

<Rule Name="DoWhileLoopRule" CheckId="HE3346">

<Context>Do While Loop Should Not Contain Variable Declaration.</Context>

<Description>Do While Loop Should Not Contain Variable Declaration.</Description>

</Rule>

  • Enum should require Default Value.

<Rule Name="EnumDefaultRule" CheckId="HE3351">

<Context>Enum Should require Default Value.</Context>

<Description>Enum Should require Default Value.</Description>

</Rule>