Getting Started with NDepend API

Introduction

The NDepend.API.dll assembly can be found in $NDependInstallPath$\Lib directory.

It contains a set of interfaces to develop custom sophisticated .NET static analysis tools. NDepend.API is related to CQLinq and NDepend.PowerTools:

  • CQLinq (Code Query through Linq) is the ability of NDepend to edit and run live linq queries (C# syntax) based on NDepend.CodeModel types, contained in NDepend.API.dll. NDepend proposes out-of-the-box around 200 code queries and rules based on CQLinq that can be used and customized easily.
  • NDepend.PowerTools are a set of short open-source static analyzers, packed into the Visual Studio solution: $NDependInstallPath$\NDepend.PowerTools.SourceCode\NDepend.PowerTools.sln

To summarize:

  • CQLinq is a facility to develop easily lightweight static analyzer.
  • NDepend.API can be used to develop more sophisticated static analyzer.
  • NDepend.PowerTools demonstrate NDepend.API usage and capabilities. More details about the power tools can be found here.

With NDepend.API it is easy to programmatically:

  • Analyze one or several .NET assemblies and create NDepend reports (only with Build Machine licensing)
  • Explore dependencies between this assemblies namespaces, types, methods and fields
  • Gather usual code metrics, computed by NDepend on code elements, and create your own code metrics
  • Explore diff between two versions analyzed of a .NET code base, and even follow evolution across many versions
  • Open source file declaration(s) of a code element
  • Generate on the fly and execute CQLinq rules or queries

Which license is required to run a program using NDepend.API?

100% of NDepend API can be used from:

Almost all the NDepend API can be used with a Developer license, the only exception being these two API methods:

Running an analysis and building a report can be done with Developer licensing only by clicking a button in the UI. Only the Build-Machine licensing can run automatically an analysis and automatically build a report.

NDepend API cannot be used with an Azure DevOps / TFS Extension license.

Getting started with NDepend API

Certainly the best way to get started with the NDepend.API infrastructure is to use the Power Tools and browse their source code packed in the Visual Studio solution: $NDependInstallPath$\NDepend.PowerTools.SourceCode\NDepend.PowerTools.sln. You can tweak existing power tools or create your own power tools.

Certainly the best way to get started with reading the code model from the NDepend.API, which types are in the namespace NDepend.CodeModel, is to browse default CQLinq code queries and code rules written on top of the code model.

The NDepend.API.dll assembly can be found in $NDependInstallPath$\Lib directory. You'll only need to reference this single assembly to use the NDepend.API.

The NDepend.API.dll assembly is an access point to most of NDepend features. Internally this assembly relies on most of others NDepend assemblies. As a consequence, using this assembly cannot be as easy as copy/pasting it. At the end of this documentation, see further explanations on How to build and deploy your program using NDepend.API.

The file $NDependInstallPath$\NDepend.PowerTools.SourceCode­\NDepend_API_GettingStarted_Program.cs shows the basic code to get started with NDepend.API, here is the most relevant part. Types and methods linked are the backbone of NDepend.API:

using System;
using System.Collections.Generic;
using System.Linq;
 
using NDepend.Analysis;
using NDepend.CodeModel;
using NDepend.Path;
using NDepend.Project;
 
namespace YourNamespace {
   class Program {
 
      public static void Main() {
         // 0) Creates a NDependServicesProvider object
         var ndependServicesProvider = new NDependServicesProvider();
 
 
         // 1) obtain some VS solution or project file path
         var visualStudioManager = ndependServicesProvider.VisualStudioManager;
         ICollection<IAbsoluteFilePath> vsSlnOrProjFilePaths;
         // get a non IntPtr.Zero owner Window Handle. 
         // If you are in a System.Console try this.
         IntPtr ownerWindowHandle = ...; 
         visualStudioManager.ShowDialogSelectVisualStudioSolutionsOrProjects(
            ownerWindowHandle, out vsSlnOrProjFilePaths);
         // Could also use:  
         //  visualStudioManager.GetMostRecentlyUsedVisualStudioSolutionOrProject()
 
 
         // 2) obtains assemblies file path to analyze
         var assembliesFilePath = 
            (from vsSlnOrProjFilePath in vsSlnOrProjFilePaths
             from assembliesFilePathTmp in 
visualStudioManager.GetAssembliesFromVisualStudioSolutionOrProject(vsSlnOrProjFilePath)
             select assembliesFilePathTmp).Distinct().ToArray();
 
         // 3) gets or create a IProject object
         var projectManager = ndependServicesProvider.ProjectManager;
         IProject project = projectManager.CreateTemporaryProject(
            assembliesFilePath, 
            TemporaryProjectMode.Temporary);
         // Or, to get a IProject object, could also use
         //   var project = projectManager.CreateBlankProject()  to create a new project
         //   and then project.CodeToAnalyze.SetApplicationAssemblies(...)
         //    or project.CodeToAnalyze.SetIDEFiles(
         //        new [] { new IDEFile("C:\File.sln".ToAbsoluteFilePath()","-test") } )
         //   and then projectManager.SaveProject(project);   to save the project file
         //
         // Or, to get an existing IProject object, could also use
         //    projectManager.ShowDialogChooseAnExistingProject(out project)
         // Or programmatically list most recently used NDepend projects on this machine
         // with this call:
         //    projectManager.GetMostRecentlyUsedProjects()
 
 
         // 4) gets an IAnalysisResult object from the IProject object
         //    **> Both RunAnalysis() and RunAnalysisAndBuildReport() methods 
         //        work only with a Build Machine license
         IAnalysisResult analysisResult = project.RunAnalysis();
         //  Or  project.RunAnalysisAndBuildReport()  
         
 
         // Or, to get a IAnalysisResult object, first gets a IAnalysisResultRef object, 
         // that represents a reference to a persisted IAnalysisResult object
         // then call
         //    project.TryGetMostRecentAnalysisResultRef() or 
         //    project.GetAvailableAnalysisResultsRefs() or 
         //    project.GetAvailableAnalysisResultsRefsGroupedPerMonth()
         // and then call 
         //    analysisResultRef.Load()
 
 
         // 5) gets a ICodeBase object from the IAnalysisResult object
         ICodeBase codeBase = analysisResult.CodeBase;
         // Or eventually use a ICompareContext object if you wish to analyze
         // the code delta between two ICodeBase.
         // codeBase.CreateCompareContextWithOlder(olderCodeBase)
 
 
         // 6) Use the code model API to query code and do develop any algorithm you need!
         //    For example here we are looking for complex methods
         var complexMethods = (from m in codeBase.Application.Methods
                               where m.ILCyclomaticComplexity > 10
                               orderby m.ILCyclomaticComplexity descending
                               select m).ToArray();
         if (complexMethods.Length == 0) { return; }
         Console.WriteLine(
            $"Press a key to show the {complexMethods.Length} most complex methods");
         Console.ReadKey();
         foreach (var m in complexMethods) {
            Console.WriteLine(
               $"{m.FullName} has a IL cyclomatic complexity of {m.ILCyclomaticComplexity}");
         }
 
 
         // 7) eventually lets the user opens source file declaration
         if (complexMethods.First().SourceFileDeclAvailable) {
            var mostComplexMethod = complexMethods.First();
            Console.WriteLine(
               "Press a key to open the source code decl of the most complex method?");
            Console.ReadKey();
            mostComplexMethod.TryOpenSource();
            // Eventually use 
            //   ExtensionMethodsTooling.TryCompareSourceWith(
            //     NDepend.CodeModel.ISourceFileLine,
            //     NDepend.CodeModel.ISourceFileLine)
            // to compare 2 different versions of a code element
         }
      }
   }
}

How to build and deploy your programs that use NDepend.API?

To create your own program that references the assembly NDepend.API.dll there are a few requirements:

  • NDepend.API.dll Reference Properties (in Visual Studio) must have Copy Local set to False.

    visual studio project copy local equals false

  • You must include in your project the source file $NDependInstallPath$\NDepend.PowerTools.SourceCode\AssemblyResolver.cs to load NDepend.API.dll at runtime.
    In your Main() method you must use AssemblyResolver this way (see the code sample below).
    Notice that if your executable is not generated in $NDependInstallDir$ the relative path .\Lib (highlighted in the code below) must be updated to provide a relative path to this directory.
    using System;
    using System.Linq;
    using System.Runtime.CompilerServices;
     
    using NDepend;
    using NDepend.Analysis;
    using NDepend.Path;
    using NDepend.PowerTools;
    using NDepend.Project;
     
    namespace YourNamespace {
       class Program {
     
          // ************************** IMPORTANT ***********************************
          // All programs using NDepend.API.dll should have this type AssemblyResolver
          // parametrized with the relative path to the dir "$NDependInstallDir$\Lib".
          // Since  NDepend.PowerTool.exe  is in the dir "$NDependInstallDir$"
          // the relative path is @".\Lib"
          private static readonly AssemblyResolver s_AssemblyResolver = new AssemblyResolver(@".\Lib");
     
          [STAThread]
          static void Main() {
             AppDomain.CurrentDomain.AssemblyResolve += s_AssemblyResolver.AssemblyResolveHandler;
             MainSub();
          }
     
          // Need this MethodImplAttribute to make sure that AssemblyResolve
          // has been registered successfully before JITing any method
          // that uses anything from the NDepend.API
          [MethodImpl(MethodImplOptions.NoInlining)]
          static void MainSub() {
             var ndependServicesProvider = new NDependServicesProvider();
     
             // Example of using the NDepend.API
             var projectManager = ndependServicesProvider.ProjectManager;
             IProject project = projectManager.LoadProject(
                @"C:\YourPathToYourNDependProjectFile\File.ndproj".ToAbsoluteFilePath());
     
             // Try to load the most recent analysis result
             var refs = project.GetAvailableAnalysisResultsRefs();
             if (!refs.Any()) { return; }
             IAnalysisResult result = refs[0].Load();
     
             // Obtain source files paths from the analysis result code base model 
             // For example this can be done this way:
             var sourceFiles = result.CodeBase.Application.CodeElements
                .Where(c => c.SourceFileDeclAvailable)
                .SelectMany(c => c.SourceDecls)
                .Select(c => c.SourceFile.FilePath)
                .Distinct()
                .ToList();
     
             // Print all source files paths
             foreach (IAbsoluteFilePath sourceFilePath in sourceFiles) {
                Console.WriteLine(sourceFilePath.ToString());
             }
          }
       }
    }
  • NDepend assemblies other than NDepend.API.dll contain the code behind the interfaces defined in NDepend.API.dll, hence all NDepend assemblies must be present in $NDependInstallPath$\ and $NDependInstallPath$\Lib.
  • To redeploy your program that references the assembly NDepend.API.dll, NDepend licenses files must be deployed as well. This implies the respect of Developer and Build Machine seats licensing.
  • As an alternative of using the class AssemblyResolver you can create an App.Config file for your executable assembly, and add a <probing> element that refers to $NDependInstallPath$\Lib (like for example C:\NDepend\Lib). When the CLR won't resolve an assembly, then it'll look at dir in such element