Tutorial: Extending Existing Rules

You already know how you can create new custom rules using CodeIt.Right SDK, of not, you may want to read this article first.

In this tutorial we will show how to extend existing rules using the new SDK features introduced in CodeIt.Right v1.8.09300. The new technique is to derive the custom rule from a base rule and override only the methods that need to be modified.

Let's extend the rule "Avoid single line If statement" (RuleID: CD0002; base class AvoidSingleLineIfStatement) in the Coding Style category:

ExtendingRules1

 

The base rule above will trigger a violation when find a code pattern like this:

if (x > 0) y = 0;

When the rule default correction action "Split If block into a multiline statement" is used it will modify the code into:

 

if (x > 0)
{
    y = 0;
}

That is what comes in the box.

Our team may have an internal guideline that requires a single line conditional to be split into two lines without the squiggly brackets:

if (x > 0)
    y = 0;

 

Hey, not all teams are created equal :)

In this case correction option that comes with the base rule AvoidSingleLineIfStatement is not very useful and we need to extend the rule with our custom correction.

So, let's start.

We will skip the list of required referenced  assemblies - you will find them in the complete code sample at the end of the post.

Declare new class that inherits from the base rule class:

namespace CustomRuleLibrary.CodingStyle 

    [Serializable] 
    [ModificationDate("10/14/2009")] 
    [Category("CustomRuleLibrary.CodingStyle")] 
    public class AvoidSingleLineIfStatement2 : AvoidSingleLineIfStatement   // Must reference SubMain.CodeItRight.Rules.CodingStyle.dll for this  
    {

In the code above we also set the Category attribute to "CustomRuleLibrary.CodingStyle" so we could easier find the custom in the Profile.

We override ID property and use GUID for the new RuleID value. Some of the CodeIt.Right functions, for example, help pages are handled differently for custom rules. Using GUID for RuleID hits CodeIt.Right that the rule is custom.

        public override string ID 
        { 
            get 
            {  
                // Override the RuleID value with own GUID (recommended) 
                return "6C832235-0B02-4227-911A-156F0B1FAC71"
            } 
        } 

Property AutoCorrectionOptions needs to be overridden to add new correction action "Split If block into a multiline statement without block":

        /// <summary> 
        /// Returns list of auto correction options 
        /// </summary> 
        public override string[] AutoCorrectionOptions 
        { 
            get 
            { 
                IMethodInfo methodInfo = Element as IMethodInfo; 
                if (methodInfo != null
                { 
                    // Get the base class list of available corrections 
                    string[] baseOptions = base.AutoCorrectionOptions; 
                    string[] options = (string[])Array.CreateInstance(typeof(string), baseOptions.Length + 1); 
                    // Add new correction action to the list 
                    Array.Copy(baseOptions, 0, options, 0, baseOptions.Length); 
                    options[options.Length - 1] = "Split \"If\" block into a multiline statement without block"

                    return options; 
                } 

                return new string[] { }; 
            } 
        }

The base rule correction option:

ExtendingRules2

Our custom rule correction options with the new action added in the AutoCorrectionOptions above:

ExtendingRules3

Now we implement our new custom correction action just the way we like it to work:

        /// <summary> 
        /// Implementation of our custom correction action  
        /// </summary> 
        private bool Correct_1(IElementInfo elementInfo) 
        { 
            IMethodInfo methodInfo = Element as IMethodInfo; 
            if (methodInfo != null
            { 
                StatementCollection statements = RuleUtilities.GetMethodStatements(methodInfo); 
                IStatement statement = SearchForSingleLineIfStatement(statements); 
                if (statement != null
                { 
                    string codeToInsertAfter = Environment.NewLine; 
                    string codeToInsertBefore = Environment.NewLine; 

                    IRegion ifBlockRegion = GetIfBlockRegion(statement); 
                    if (ifBlockRegion != null
                    { 
                        string ifBlockText = GetIfBlockText(statement, ifBlockRegion); 
                        if (ifBlockText != null
                        { 
                            methodInfo.ReplaceCodeBlock(ifBlockRegion, 
                                codeToInsertBefore + ifBlockText + codeToInsertAfter); 

                            return true
                        } 
                    } 
                } 
            } 

            return false
        }

And we tie it all together in the method Correct - for correction action with index 0 we execute the base rule correction and for the index 1 (that we added) execute method Correct_1 that implements our custom correction:

        /// <summary> 
        /// Execute the specified correction action 
        /// </summary> 
        /// <param name="correctionOptionIndex">Index of the correction action</param> 
        public override bool Correct(int correctionOptionIndex) 
        { 
            bool result = base.Correct(correctionOptionIndex); 

            switch (correctionOptionIndex) 
            { 
                case 0
                    // For correction action 0 execute the base class correction 
                    return result; 
                case 1
                    // For correction action 1 execute our custom correction 
                    return Correct_1(Element); 

                default
                    break
            } 

            return false
        }

And we are done!

The final steps are to compile the new rule, drop the assembly to Program Files\SubMain\CodeIt.Right\Rules\ and we can now add the rule to a profile:

ExtendingRules4

Here is the complete code for our custom rule:

using System;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
using System.Runtime.Serialization;
using System.Globalization;

using SubMain.CodeObjectModel.Reflection;
using SubMain.Core.Services;
using SubMain.CodeItRight.Sdk;
using SubMain.CodeItRight.Sdk.Core.Collections;
using SubMain.CodeItRight.Sdk.Core.Reflection;
using SubMain.CodeItRight.Sdk.Core.Rules;
using SubMain.CodeItRight.Sdk.Core.Utils;
using SubMain.CodeItRight.Sdk.Rules;
using SubMain.CodeItRight.Sdk.Utils;
using SubMain.CodeItRight.Rules.CodingStyle;

namespace CustomRuleLibrary.CodingStyle
{
    [Serializable]
    [ModificationDate("10/14/2009")]
    [Category("CustomRuleLibrary.CodingStyle")]
    public class AvoidSingleLineIfStatement2 : AvoidSingleLineIfStatement   // Must reference SubMain.CodeItRight.Rules.CodingStyle.dll for this
    {
        /// <summary>
        /// Returns list of auto correction options
        /// </summary>
        public override string[] AutoCorrectionOptions
        {
            get
            {
                IMethodInfo methodInfo = Element as IMethodInfo;
                if (methodInfo != null)
                {
                    // Get the base class list of available corrections 
                    string[] baseOptions = base.AutoCorrectionOptions;
                    string[] options = (string[])Array.CreateInstance(typeof(string), baseOptions.Length + 1);
                    // Add new correction action to the list
                    Array.Copy(baseOptions, 0, options, 0, baseOptions.Length);
                    options[options.Length - 1] = "Split \"If\" block into a multiline statement without block";

                    return options;
                }

                return new string[] { };
            }
        }

        /// <summary>
        /// Implementation of our custom correction action 
        /// </summary>
        private bool Correct_1(IElementInfo elementInfo)
        {
            IMethodInfo methodInfo = Element as IMethodInfo;
            if (methodInfo != null)
            {
                StatementCollection statements = RuleUtilities.GetMethodStatements(methodInfo);
                IStatement statement = SearchForSingleLineIfStatement(statements);
                if (statement != null)
                {
                    string codeToInsertAfter = Environment.NewLine;
                    string codeToInsertBefore = Environment.NewLine;

                    IRegion ifBlockRegion = GetIfBlockRegion(statement);
                    if (ifBlockRegion != null)
                    {
                        string ifBlockText = GetIfBlockText(statement, ifBlockRegion);
                        if (ifBlockText != null)
                        {
                            methodInfo.ReplaceCodeBlock(ifBlockRegion,
                                codeToInsertBefore + ifBlockText + codeToInsertAfter);

                            return true;
                        }
                    }
                }
            }

            return false;
        }

        public override string ID
        {
            get
            { 
                // Override the RuleID value with own GUID (recommended)
                return "6C832235-0B02-4227-911A-156F0B1FAC71";
            }
        } 

        /// <summary>
        /// Execute the specified correction action
        /// </summary>
        /// <param name="correctionOptionIndex">Index of the correction action</param>
        public override bool Correct(int correctionOptionIndex)
        {
            bool result = base.Correct(correctionOptionIndex);

            switch (correctionOptionIndex)
            {
                case 0:
                    // For correction action 0 execute the base class correction
                    return result;
                case 1:
                    // For correction action 1 execute our custom correction
                    return Correct_1(Element);

                default:
                    break;
            }

            return false;
        }

    }
}

Further Reading and Resources