I recently decided to clean up an open source project, and the best tool I found for the job was Telerik JustCode’s Code Clean option. If you’ve never tried it, open the JustCode Options, go to Code Cleaning, then use the drop down to select Full (built-in) and press the “Set as Default” button.

image

If you wish, you can use the “New” or “Clone” button to set up your own profile that matches your coding style. Afterwards, return to a piece of code file and press Ctrl+Shift+Alt+F and watch your (co-worker’s) code be processed into a thing of beauty.

Opening a thousand C# files to press a button combo isn’t likely to be high on anyone’s list. Luckily, you don’t need to do this. Highlight what you want to clean in the Solution Explorer, right-click to bring up the context menu, and select Just Clean All.

image

I can’t guarantee this level of success every time, but a change I made in a project broke a ton of code due to invalid references, generating over 2000 errors. Instead of rolling back, I used Just Clean All on the solution; it fixed every last one.

However, there were some things I wish I could clean up automatically. The JustCode team prioritizes based on the impact each item will have to the community at large, and I am somewhat pickier than others. So, I asked Deyan Varchev for an example of creating a Code Cleaning extension, and he delivered one to me. The information you’ll find here isn’t even in the JustCode API templates! This means it may be unsupported or subject to change.

Remove Region Cleaning

Some people swear by regions, other people consider it a code smell. If you are a fan of regions, you can still follow this example, but you may want to uncheck this cleaning step in your code cleaning profile.

As with any JustCode extension, it is best to create it using the JustCode Extension Template. This will include the appropriate references and debug options. Now, add a new class. I called mine RemoveRegionCleaning. This class will inherit from CodeCleaningStepProviderModuleBase found in Telerik.JustCode.CommonLanguageModel. Use the Visual Aid to Create Stubs for Required Members. This will create overrides for the property CodeCleaningSteps and method ExecuteCodeCleaningStep.

Before this code is implemented, add the following two attributes to the class so it is properly exported by MEF.

[Export(typeof(IEngineModule))]
[Export(typeof(ICodeCleaningStepDefinition))]

Implementing CodeCleaningSteps

The CodeCleaningSteps property returns an IEnumerable<CodeCleanStep> which adds the cleaning for the languages specified. Since regions are present in both C# and VB, there are three constants to define for the class.

private const string MarkerId = "RemoveRegions";
private const string CleanOptionText = "Remove Regions";
private const int Order = 100;

The MarkerId is used to identify this cleaning, and the CleanOptionText defines the description provided in the Code Cleaning options. Finally, Order determines the order in which these steps run in regards to other cleaning steps. This is useful when one step will affect another.

Implementing the CodeCleaningSteps property is straightforward: create a getter that yield returns each step. Here it is:

public override IEnumerable<CodeCleaningStep> CodeCleaningSteps
{
  get
  {
    yield return new CodeCleaningStep(LanguageNames.CSharp, Order,
    CleanOptionText, MarkerId);
    yield return new CodeCleaningStep(LanguageNames.VisualBasic, Order,
    CleanOptionText, MarkerId);
  }
}

Implementing ExecuteCodeCleaningStep

The ExecuteCodeCleaningStep method performs the actions necessary to clean the code. Basically, it is the meat of this class.

public override void ExecuteCodeCleaningStep(CodeCleaningStep step, FileModel fileModel, CodeSpan span)

Regions are preprocessor directives, and we can use the fileModel parameter to retrieve every line that is one of these by calling fileModel.All<IPreProcessorDirectiveLine>(). However, we need to filter these out specifically for regions. We can analyze the text of each line to do so, and I may want to use this in the future. Therefore, I created an extension method for IPreProcessorDirectiveLine.

public static bool IsRegion(this IPreProcessorDirectiveLine line)
{
  return line.ExistsTextuallyInFile &&
    (line.Text.Trim().ToLower().StartsWith("#region ") ||
    line.Text.Trim().ToLower().StartsWith("#endregion"));
}

I will also need a way to delete these lines. Each line has a TextualCodeSpan property, which can then be replaced with String.Empty. The default formatting rules will remove the empty line, so this isn’t something this cleaning step needs to do. I want IPreProcessorDirectiveLines to have the ability to delete themselves, so I added another extension method.

public static void Delete(this IPreProcessorDirectiveLine line)
{
  line.FileModel.ReplaceTextually(line.TextualCodeSpan, String.Empty);
}

I’m going to run Delete on every processor directive. To keep the code concise, I implemented the ForEach extension method.

public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action)
{
  foreach (T item in sequence)
  {
    action(item);
  }
}

With all these steps in place, implementing the ExecuteCodeCleaningStep method is straightforward.

public override void ExecuteCodeCleaningStep(CodeCleaningStep step, FileModel fileModel,
  CodeSpan span)
{
  fileModel.All<IPreProcessorDirectiveLine>()
        .Where(line => line.IsRegion())
        .ForEach(region => region.Delete());
}

Full Source Code

 

[Export(typeof(IEngineModule))]
[Export(typeof(ICodeCleaningStepDefinition))]
public class RemoveRegionsCleaning : CodeCleaningStepProviderModuleBase
{
    private const string MarkerId = "RemoveRegions";
    private const string CleanOptionText = "Remove Regions";
    private const int Order = 100;
 
    public override IEnumerable<CodeCleaningStep> CodeCleaningSteps
    {
        get
        {
            yield return new CodeCleaningStep(LanguageNames.CSharp, Order, CleanOptionText, MarkerId);
            yield return new CodeCleaningStep(LanguageNames.VisualBasic, Order, CleanOptionText, MarkerId);
        }
    }
 
    public override void ExecuteCodeCleaningStep(CodeCleaningStep step, FileModel fileModel, CodeSpan span)
    {
        fileModel.All<IPreProcessorDirectiveLine>()
                 .Where(line => line.IsRegion())
                 .ForEach(region => region.Delete());
    }
}
 
public static class EnumerableExtensions
{
    public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action)
    {
        foreach (T item in sequence)
        {
            action(item);
        }
    }
}
 
public static class PreProcessorDirectiveLineExtensions
{
    public static bool IsRegion(this IPreProcessorDirectiveLine line)
    {
        return line.ExistsTextuallyInFile &&
               (line.Text.ToLower().Trim().StartsWith("#region ") ||
                line.Text.ToLower().Trim().StartsWith("#endregion"));
    }
 
    public static void Delete(this IPreProcessorDirectiveLine line)
    {
        line.FileModel.ReplaceTextually(line.TextualCodeSpan, String.Empty);
    }
}

 

Conclusion

That’s really all there is to it. After implementing this code, you can see it in action by debugging your JustCode extension library. Visual Studio will appear with the extensions installed, and you can try it out on any project. Just make sure you have selected the cleaning step for your code cleaning profile.

Are there other extensions you would like to see implemented? Let us know what’s on your mind in the forums!


About the Author

Chris Eargle

is a Microsoft C# MVP with over a decade of experience designing and developing enterprise applications, and he runs the local .NET User Group: the Columbia Enterprise Developers Guild. He is a frequent guest of conferences and community events promoting best practices and new technologies. Chris is a native Carolinian; his family settled the Dutch Form region of South Carolina in 1752. He currently resides in Columbia with his wife, Binyue, his dog, Laika, and his three cats: Meeko, Tigger, and Sookie. Amazingly, they all get along... except for Meeko, who is by no means meek.

Related Posts

Comments