Thursday, February 23, 2012

Unit Test looking for missing files in your project


When you're working with a big team of developers and a nice Build Server, a lot of times you rather work on a separate feature branch not to break the build while committing. This is very comfortable since you have your own "work zone" and don't bother anybody else, nor have to worry about breaking the build until you're done with your feature and ready to reintegrate to the trunk (or your main working branch).

But this comes with a price...
It's not always easy dealing with all the conflicts when reintegrating your branch, and sometimes this can lead to problems. One of the main problems we ran into a couple of times recently at my work place while doing this process was that while reintegrating, we missed some new files that were added to the project while dealing with the csproj file conflicts.

This means we had new files added to the project, and were in the project directory, but not in the '.csproj' file. This might be a class that is referenced throughout the project, which in this case you're lucky and the project won't compile, which will immediately let you know there was a problem. But if you're not lucky, this will be a controller class file, or an 'aspx' file, and leaving it out will never cause a problem for the build server (if your project isn't 100% covered with tests - a totally different subject for a whole new post!).

So... what's the easiest way to quickly solve this problem ? Write a test that will check this! :)
That's exactly what I recently did, and now every time this happens the build server will fail, with a nice message that shows you all the files you left out!
(of course, you need to make sure you didn't by accidentally leave this test out of the csproj!! :)

For your unit testing pleasures -

[Test]
public void CheckingThatAllFilesAreInCsProj()
{
    const string projectName = "TestWebsite";
    var projectDirectory = GetProjectDirectory(projectName);
    var projMissingFiles = RetrieveMissingFilesList(projectDirectory.FullName, projectName);

    Assert.IsTrue(projMissingFiles.Count() == 0,
        "There are files that are missing in the .csproj file : \n" + string.Join("\n ", projMissingFiles));
}

private DirectoryInfo GetProjectDirectory(string projectName)
{
    var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
    while (!currentDirectory.GetDirectories("*", SearchOption.TopDirectoryOnly).Any(x => x.Name == projectName))
    {
        currentDirectory = currentDirectory.Parent;
    }
    return new DirectoryInfo(currentDirectory.FullName + "\\" + projectName);
}

private string[] RetrieveMissingFilesList(string projectDirectory, string projectName)
{
    var csFiles = Directory.GetFiles(projectDirectory, "*.cs", SearchOption.AllDirectories);
    var aspxFiles = Directory.GetFiles(projectDirectory, "*.aspx", SearchOption.AllDirectories);
    var projFiles = csFiles.Union(aspxFiles);

    var csprojDocument = XDocument.Load(projectDirectory + "\\" + projectName + ".csproj");
    var csFileElements = GetCsFileElements(csprojDocument);
    var contentFileElements = GetContentFileElements(csprojDocument);
    var allFileElements = csFileElements.Union(contentFileElements);

    var projMissingFiles = new List<string>();
    foreach (var file in projFiles)
    {
        var stripFileName = file.Replace(projectDirectory, "");

        if (!allFileElements.Any(x => x.Attribute("Include").Value.ToLower() == file.Replace(projectDirectory + "\\", "").ToLower()))
            projMissingFiles.Add(stripFileName);
    }

    return projMissingFiles.ToArray();
}

private static IEnumerable<XElement> GetContentFileElements(XDocument csprojDocument)
{
    return GetFileElements(csprojDocument, "Content");
}

private static IEnumerable<XElement> GetCsFileElements(XDocument csprojDocument)
{
    return GetFileElements(csprojDocument, "Compile");
}

private static IEnumerable<XElement> GetFileElements(XDocument csprojDocument, string elementType)
{
    return csprojDocument.Elements()
        .Where(x => x.Name.LocalName == "Project").Elements()
        .Where(x => x.Name.LocalName == "ItemGroup" &&
            x.HasElements &&
            x.Elements().Where(t => t.Name.LocalName == elementType).Count() > 0).Elements();
}


The code in the test isn't the most beautiful thing I wrote but it does serve a great purpose.
All it does is search the project folder for all .cs and .aspx files, and then dissects the csproj file (which is just a big xml file) to find all the file references.
The code I posted just checks for cs/aspx files, but you can easily add functionality for js, css, html and any other file type you like...

Enjoy :)

Thursday, February 16, 2012

.net to Java transition gets a little easier for me...


I'm taking a university course in Java this semester, so I've been recently writing a lot of Java and as an IDE using IntelliJ (community edition) by JetBrains a lot. As a .net guy, mostly, I'm used to Visual Studio with ReSharper 6 and I remember so many keyboard shortcuts by heart which make my coding work so productive!

So you can guess how hard it is for someone like me, doing the transition to a different IDE, and not having ANY of the shortcuts you're used to. I tried studying some of the intelliJ shortcuts but it just got too annoying, and I finally decided to take the time and set ALL of the IntelliJ shortcuts to the same VS shortcuts once and for all. While I did that, I ran into a nice surprise - The brilliant guys at JetBrains were one step ahead of me the whole time, and have already added a VS keymap option in their settings panel!!!




Thank you JetBrains for making my Java experience just a little bit more comforting! :)

Saturday, February 11, 2012

VS Spell Check Extension - using Roslyn


A couple of months ago Microsoft published the Roslyn CTP which gives us an inside view on the Compilers view to our code. This also comes with project templates for creating CodeIssues and CodeActions (These are code suggestiongs/actions that are available to the coder in the IDE).

Since I read about this it interested me a little, and wanted to play around with this. Since I've been programming in java at home lately due to a course i'm taking at the university, I realized that one of the nice features that IntelliJ has is that it gives you suggestions on spelling mistakes you made on variable names (because this is one of the most annoying things i see in code and i truly believe makes it harder to maintain), so I thought i'd try to implement this in VS as an extension.

It was really simple to do so -

I just created a CodeIssue project :


With the ExportSyntaxNodeCodeIssueProvider attribute, you can tell it to execute the code only on variable declarations :


Split each variable name into separate words (most variables, at least when I write code looks like this "var myReceivedFilesList" so i separate them by case) :
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken = new CancellationToken())
{
    var variable = node as VariableDeclarationSyntax;
    if (variable != null)
    {
        var nodes = variable.ChildNodes().Where(x => x.Kind == SyntaxKind.VariableDeclarator);

        foreach (var syntaxNode in nodes)
        {
            var fullVariableName = syntaxNode.GetFirstToken().ValueText;

            var wordsInVariableName = fullVariableName.SplitToWords();
  
            if (wordsInVariableName != null)
            {
                var correctSpelling = CheckAndCorrectSpelling(wordsInVariableName);

                if (correctSpelling != null)
                    yield return new CodeIssue(CodeIssue.Severity.Info, syntaxNode.GetFirstToken().Span, "Possible Typo ?\nMaybe you meant : " + correctSpelling);
            }
        }
    }
}


And viola!
You have the spell check feature on variable names in your VS IDE :



For the spell check I used the NHunspell project (This is the .net wrapper to the OpenOffice dictionary).

I put the code up on google code so you can take a look at it, and who knows, maybe even contribute to it... :)
https://code.google.com/p/gb-coding-extension/

I don't know if this is production-worthy code, I didn't even check performance or if it slows down the IDE. This was just a small learning experience for me.
Maybe if I have some more free time, and good ideas, I'll add to it more features in the future...