A post on CRAp4dotNet is long over due -- my sincerest apologies for those of you that have been holding your breath. 

From last time I mentioned that one of the two hurdles to get this working for .Net was computing the code complexity.  There aren't any good existing open source, and free options out there in the tubes to do this so I had to code it up all on my own.  I took a bag of words approach, and decided to just parse the source files with regular expressions.  There's probably a better way to do this, but at least this way its pretty trivial to extend the complexity class to parse any language you might want... as long as you compile the complexity class in .Net -- for now anyways. 

 A quick review of what cyclomatic complexity for those that don't want to click through.

CC = E - N + p

Where E is the number of edges in the graph, N is the number of nodes (verticies) in the graph, and p is the number of connected components.  In the implementation of this you just end up counting decision points in the source.  So, every if, then, else, foreach, do, etc... you encounter you add 1 to the method's complexity value. 

The next thing that needs to be determined is the class' aggregate complexity.  To get this just divide the sum of each method's complexity value by the number of methods.

Here it is free to use under the MIT license,

/***

 

 * Copyright (c) 2008 Adam Scarborough

 

 * All rights reserved.

 

 *

 

 * Redistribution and use in source and binary forms, with or without

 

 * modification, are permitted provided that the following conditions

 

 * are met:

 

 * 1. Redistributions of source code must retain the above copyright

 

 *    notice, this list of conditions and the following disclaimer.

 

 * 2. Redistributions in binary form must reproduce the above copyright

 

 *    notice, this list of conditions and the following disclaimer in the

 

 *    documentation and/or other materials provided with the distribution.

 

 * 3. Neither the name of the copyright holders nor the names of its

 

 *    contributors may be used to endorse or promote products derived from

 

 *    this software without specific prior written permission.

 

 *

 

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"

 

 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE

 

 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE

 

 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE

 

 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR

 

 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF

 

 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS

 

 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN

 

 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)

 

 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF

 

 * THE POSSIBILITY OF SUCH DAMAGE.

 

 */

 

 

 

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

using System.Collections;

using System.Text.RegularExpressions;

 

 

 

namespace complexityTest

{

    class CyclomaticComplexity

    {

        public static decimal fileComplexity = 1;

        public static decimal methodCount = 0;

 

        public static void Complexity(string path)

        {

            try

            {

                if (path.EndsWith(".cs") || path.EndsWith(".cpp") || path.EndsWith(".c"))

                {

                    ComplexityCalculator(path);

                }

                else

                {

                    DirectoryParser(path);

                }

            }

            catch (Exception e)

            {

                Console.WriteLine("Path not found:  " + path);

                Console.WriteLine(e);

            }

        }

 

        private static void DirectoryParser(string projectDirectoryPath)

        {

            // Process the list of files found in the directory.

            string[] fileEntries = Directory.GetFiles(projectDirectoryPath);

            foreach (string fileName in fileEntries)

                ComplexityCalculator(fileName);

              

          

            // Recurse into subdirectories of this directory.

            string[] subdirectoryEntries = Directory.GetDirectories(projectDirectoryPath);

            foreach (string subdirectory in subdirectoryEntries)

                DirectoryParser(subdirectory);

        }

 

        private static decimal ComplexityCalculator(string fileName)

        {

           

            if (fileName.EndsWith(".cs") || fileName.EndsWith(".cpp") || fileName.EndsWith(".c"))

            {

                try

                {

                    using (StreamReader sourceCode = new StreamReader(fileName))

                    {

                        String linesOfSource = sourceCode.ReadToEnd();

                       

                        methodCount = methodCount + FindNumberOfMethodsInClasses(linesOfSource);

                        fileComplexity = fileComplexity + Counter(linesOfSource);

                        return fileComplexity;

                    }

                  

                }

                catch (Exception e)

                {

                    Console.WriteLine("There was an error reading the source file:  " + fileName);

                    Console.WriteLine(e);

                    return 0;

                }

            }

            else

            {

                Console.WriteLine("Unsupported file type:  " + fileName);

                return 0;

            }

        }

 

        

        private static decimal Counter(string theSourceFileTurnedIntoABigString)

        {

            decimal complexityCount = 0;

            String[] matchString = { "(if)(?(\\s)\\(|\\()", "else", "(&&)", "(\\|\\|)", "(foreach)",

                                       "(\\(for)", "(do)", "(while)", "(switch)", "(case)(?(\\s)\\(|\\()",

                                       "(try)(?(\\r\\n)\\{|\\{)", "(catch)(?(\\s)\\(|\\()", "(\\?\\:)",

                                       "(else if)(?(\\s)\\(|\\()" };

           

            foreach (string regexSelectorString in matchString)

            {

                Regex selector = new Regex(regexSelectorString);

                MatchCollection selectorMatch = selector.Matches(theSourceFileTurnedIntoABigString);

                complexityCount += selectorMatch.Count;

            }

           

            return complexityCount;

        }

    }

}

 

The next step is to add a couple of methods to compute the aggregate complexity.  For that there will need to be two methods added. One to find the number, and start/end locations of the classes, and one to count the methods in the classes.