Click here to Skip to main content
15,885,216 members
Articles / Programming Languages / C#

Resolving Symbolic References in a CodeDOM (Part 7)

Rate me:
Please Sign up or sign in to vote.
4.75/5 (6 votes)
2 Dec 2012CDDL12 min read 19.4K   509   14  
Resolving symbolic references in a CodeDOM.
// Nova.CodeDOM usage examples.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Mono.Cecil;

using Nova.CodeDOM;    // The CodeDOM namespace
using Nova.Utilities;  // Handy utility methods

namespace Nova.Examples
{
    /// <summary>
    /// This program contains examples for using the Nova.CodeDOM C# object model library.
    /// </summary>
    class Program
    {
        #region /* MAIN METHOD */

        private static string _baseDirectory;
        private static string _executionDirectory;

        static void Main(string[] arguments)
        {
            _executionDirectory = Directory.GetCurrentDirectory();
            _baseDirectory = FileUtil.GetBaseDirectory();

            // Allow the base directory to be overridden (where it looks for files to be loaded)
            if (arguments != null && arguments.Length > 0)
                _baseDirectory = arguments[0];

            // Determine the application name and if we're running in VS
            string appName = AppDomain.CurrentDomain.FriendlyName;
            if (appName.EndsWith(".exe"))
                appName = appName.Substring(0, appName.Length - 4);
            if (appName.EndsWith(".vshost"))
                appName = appName.Substring(0, appName.Length - 7);

            // Change from the output directory to the project root, so the examples can find project files
            Directory.SetCurrentDirectory(_baseDirectory);

            Version appVersion = typeof(Program).Assembly.GetName().Version;
            string appDescription = appName + " " + appVersion.Major + "." + appVersion.Minor;
            Console.WriteLine(appDescription + "  -  C# CodeDOM Examples.\nCopyright (C) 2011-2012 Inevitable Software, all rights reserved.");

            if (FileUtil.FindFile("Nova.Examples.sln") == null)
            {
                Console.WriteLine("\nCouldn't find the 'Nova.Examples.sln' file - please specify the path of this file as a command-line argument.");
                return;
            }

            // Execute all public examples in this class
            Log.WriteLine("\nExecuting Nova.CodeDOM examples...\n");
            MethodInfo[] methodInfos = Assembly.GetExecutingAssembly().GetType("Nova.Examples.Program").GetMethods(BindingFlags.Public | BindingFlags.Static);
            foreach (MethodInfo methodInfo in methodInfos)
            {
                Log.WriteLine("===== Executing Example: " + methodInfo.Name + " =====");
                try
                {
                    methodInfo.Invoke(null, null);
                }
                catch (Exception ex)
                {
                    Log.Exception(ex, "executing example");
                }
                Log.WriteLine("===== " + methodInfo.Name + " Completed =====\n");
            }

            // Wait for a key press before exiting if running in VS
            if (Debugger.IsAttached)
            {
                Console.WriteLine("DONE.  Press any key to exit.");
                Console.ReadKey();
            }
        }

        #endregion

        #region /* LOGGING & CONFIGURATION NOTES */

        // Nova.CodeDOM includes a logging class that logs status messages to the Console Window by default.
        // It uses this for it's own logging, but you might also find it useful.
        // Use the Log class to log your own messages like this:
        //     Log.WriteLine("message");
        //     Log.DetailWriteLine("detail message");  // Only logged if LogLevel is set to Detailed
        //     Log.Exception(ex, "message");  // Log an Exception object and message

        // To intercept these log messages, you may install your own handler method of type 'void (string, string)':
        //     Log.SetLogWriteLineCallback(LoggingMethod);

        // To change the level of logging, or turn it off, set the LogLevel property, like this:
        //     Log.LogLevel = Log.Level.Detailed;
        //     Log.LogLevel = Log.Level.None;

        // NOTE: If you do not intercept or turn off logging, it might slow down processing considerably if
        //       many messages are being sent to the console.

        // Various runtime options are available in a config file, and are documented there.  See the App.config
        // file as an example and a template for your own config file. You may wish to move some or all of these options
        // to the config file of the application that uses Nova.CodeDOM.
        // All configuration options may also be controlled manually at runtime by setting the appropriate static class
        // member, such as:
        //     Log.LogLevel = Log.Level.Detailed;
        //     Resolving.Resolver.ExactMatching = false;
        //     CodeObject.UseTabs = true;
        //     CodeObject.AutomaticFormattingCleanup = false;
        //     CodeObject.AutomaticCodeCleanup = false;

        #endregion

        #region /* LOADING EXAMPLES */

        /// <summary>
        /// Load an entire Solution, parsing and resolving it.
        /// </summary>
        public static void LoadSolution()
        {
            // Load a solution, specifying the configuration and platform - these are optional, but the configuration can affect
            // conditionally compiled code, and the platform will determine the output directory used to load generated files.
            // Also, turn on logging of messages.
            Solution solution = Solution.Load("Nova.Examples.sln", "Debug", "x86", LoadOptions.Complete | LoadOptions.LogMessages);
            if (solution != null)
                Log.WriteLine("Solution '" + solution.Name + "' successfully loaded, parsed, resolved.");

            // If successful, a Solution object is returned, otherwise null.
            // By default, a Complete load is done, but this can be overridden.
            // A 'Complete' load of a Solution goes through the following steps:
            //   - Parse the Solution file, creating a Solution object and a list of ProjectEntry objects.
            //   - Parse each Project file, creating a Project object and loading the lists of References and code files (CodeUnits).
            //   - Resolve all References for each project, and determine the project dependencies.
            //   - Load all referenced assemblies for each project (in dependency order).
            //   - Load all types from all referenced assemblies for each project (in dependency order).
            //   - Parse all code files for each project (in dependency order), first loading all types from any referenced projects.
            //   - Resolve all symbolic references in all code files for each project (in dependency order).
            // The 'LoadOnly' option stops this process after parsing the Solution and Project files and resolving References, which
            // is useful if only the solution and/or project files are being viewed, analyzed, or edited.  The 'DoNotResolve' option
            // skips resolving symbolic references, which is useful if that step is not required, such as when only formatting code
            // which doesn't rely on resolved references.

            // Source code is loaded into a semantic tree of pure code objects, without separate objects for syntax (such as braces),
            // but formatting information and comments are retained on these objects.  By default, code will be re-formatted according
            // to the default formatting rules when displayed or saved, but this can be controlled with options.  Also, newlines are
            // generally retained as-is in loaded code, so that changes will be minimal unless re-formatting is explicitly performed.
        }

        /// <summary>
        /// Load a Solution WITHOUT parsing or resolving it (useful to only analyze and/or modify the Solution and/or Projects).
        /// </summary>
        public static void LoadSolutionLoadOnly()
        {
            Solution solution = Solution.Load("Nova.Examples.sln", LoadOptions.LoadOnly);
            if (solution != null)
                Log.WriteLine("Solution '" + solution.Name + "' successfully loaded without parsing or resolving source files.");
        }

        /// <summary>
        /// Load a Solution and parse it, but don't resolve it (useful if only formatting or other processes that don't require resolving symbolic references).
        /// </summary>
        public static void LoadSolutionDoNotResolve()
        {
            Solution solution = Solution.Load("Nova.Examples.sln", LoadOptions.DoNotResolve);
            if (solution != null)
                Log.WriteLine("Solution '" + solution.Name + "' successfully loaded and parsed, but not resolved.");
        }

        /// <summary>
        /// Load a single Project, parsing and resolving it (useful for working on a single project of a solution).
        /// Any missing project references will be treated as assembly references so that symbols can be resolved.
        /// </summary>
        public static void LoadProject()
        {
            // Load a project, specifying the configuration and platform - these are optional, but the configuration can affect
            // conditionally compiled code, and the platform will determine the output directory used to load generated files.
            Project project = Project.Load("Nova.Examples.csproj", "Debug", "x86");
            if (project != null)
                Log.WriteLine("Project '" + project.Name + "' successfully loaded, parsed, resolved.");

            // If successful, a Project object is returned, otherwise null.
            // By default, a Complete load is done, but this can be overridden.
            // Loading a Project directly goes through the following steps:
            //   - Parse the Project file, creating a Project object and loading the lists of References and code files (CodeUnits).
            //   - Resolve all References for the project.
            //   - Load all referenced assemblies.
            //   - Load all types from all referenced assemblies.
            //   - Parse all code files.
            //   - Resolve all symbolic references in all code files.
            // The 'LoadOnly' option stops this process after parsing the Project file and resolving References, which is useful
            // if only the project file itself is being viewed, analyzed, or edited.  The 'DoNotResolve' option skips resolving
            // symbolic references, which is useful if that step is not required, such as when only formatting code which doesn't
            // rely on resolved references.
        }

        /// <summary>
        /// Load a single CodeUnit (source file), parsing and resolving it.
        /// </summary>
        public static void LoadCodeUnit()
        {
            // When loading a CodeUnit directly, all external symbolic references will fail to resolve, because we have no Project
            // and therefore no assembly references.  Internal references (to locals, parameters, members of the same type, etc)
            // will still be resolved.  See the LoadCodeUnits() example for how external references can be resolved.
            CodeUnit codeUnit = CodeUnit.Load("Program.cs");
            if (codeUnit != null)
                Log.WriteLine("File '" + codeUnit.Name + "' successfully loaded, parsed, resolved (except external references).");
        }

        /// <summary>
        /// Load a code fragment, parsing and resolving it.
        /// </summary>
        public static void LoadCodeFragment()
        {
            // A code fragment can be a type or method declaration, or just a chunk of code.  Formatting is not that
            // important, including newlines (although newlines - or the lack of them - will be preserved unless the
            // code is specifically re-formatted).  Invalid code will produce parse errors.
            const string fragment = "int sum = 0; for (int i = 1; i < 10; ++i) sum += i;";

            // As in the LoadCodeUnit example, external references will not be resolved since there are no assembly references.
            // See the LoadCodeUnits() example for how external references can be resolved.
            CodeUnit codeUnit = CodeUnit.LoadFragment(fragment, "Fragment");
            if (codeUnit != null)
                Log.WriteLine("Code fragment '" + codeUnit.Name + "' successfully parsed, resolved (except external references).");
        }

        /// <summary>
        /// Load multiple CodeUnits with resolving of external references.
        /// </summary>
        public static void LoadCodeUnits()
        {
            // To load one or more CodeUnits with external references resolved, create a 'dummy' parent Project for the CodeUnits,
            // and add any desired assembly references to it.
            Project project = new Project("Miscellaneous Files", null);
            CodeUnit codeUnit1 = project.AddFile("Program.cs");  // Add files like this
            CodeUnit codeUnit2 = project.AddFile("Properties/AssemblyInfo.cs");
            const string fragment = "using System; Console.WriteLine(\"TEST\");";
            CodeUnit codeUnit3 = project.CreateCodeUnit(fragment, "Fragment");  // Add code fragments like this

            // We want external references resolved in this case, so add assembly references and load their types
            project.AddDefaultAssemblyReferences();  // Add commonly used assembly references
            // Add additional assembly references like this, with an optional "hint path":
            project.AddAssemblyReference("Nova.CodeDOM", GetFilePath("Nova.CodeDOM.dll"));
            project.AddAssemblyReference("Mono.Cecil", GetFilePath("Mono.Cecil.dll"));
            // You may also set 'project.ReferencePath' to a string of semi-colon separated search paths.

            project.LoadReferencedAssembliesAndTypes();  // Load the referenced assemblies, and their public types
            project.ParseAndResolveCodeUnits();          // Parse and resolve all code units in the project

            // In this example, there is no Solution, so we can't benefit from it's single CodeAnnotations collection
            // of all messages for all source files.  We could add a dummy Solution to the Project to get this, or look
            // at the ListedAnnotations collection on each CodeUnit.  In this case, we'll just call a helper method on
            // each CodeUnit that displays message counts along with any errors or warnings.
            codeUnit1.LogMessageCounts(true);
            codeUnit2.LogMessageCounts(true);
            codeUnit3.LogMessageCounts(true);
        }

        private static string GetFilePath(string name)
        {
            string filePath = _executionDirectory + @"\" + name;
            if (!File.Exists(filePath))
                filePath = _baseDirectory + @"\" + name;
            return filePath;
        }

        #endregion

        #region /* CODE GENERATION & SAVING EXAMPLES */

        /// <summary>
        /// Generate a new Solution, Project, and CodeUnit (with code in it) and save them.
        /// </summary>
        public static void GenerateNewSolution()
        {
            // Create a new solution and project
            const string path = "Generated\\";
            Solution solution = new Solution(path + "Generated");          // file extension will default to '.sln' if omitted
            Project project = solution.CreateProject(path + "Generated");  // file extension will default to '.csproj' if omitted
            project.OutputType = Project.OutputTypes.Exe;  // Output type will default to Library if not set

            // In order to have external symbolic references resolved, assembly references must be added, then loaded.
            // However, this is not necessary in this case, since we are only saving the generated code, and UnresolvedRefs
            // will save just fine using their text names.
            //project.AddDefaultAssemblyReferences();  // Add commonly used assembly references
            //project.LoadReferencedAssembliesAndTypes();

            // Add a file to the project, and put some code in it
            CodeUnit codeUnit = project.CreateCodeUnit(path + "Program");  // file extension will default to '.cs' if omitted
            codeUnit.Add(
                new UsingDirective(project.ParseName("System")),
                new UsingDirective(project.ParseName("System.Text"))
                );

            NamespaceDecl namespaceDecl = new NamespaceDecl(project.ParseName("Generated"));
            codeUnit.Add(namespaceDecl);
            ClassDecl classDecl = new ClassDecl("Program");
            namespaceDecl.Add(classDecl);
            MethodDecl methodDecl = new MethodDecl("Main", typeof(void), Modifiers.Static, new ParameterDecl("args", typeof(string[])));
            methodDecl.Add(new Comment("Add code here"));
            classDecl.Add(methodDecl);

            // Note the use of 'project.ParseName()' in the UsingDirectives (which aren't necessary in this case, just shown as
            // examples) - this is a helper method that parses a string into an expression of NamespaceRef and/or TypeRefs and Dot
            // operators if present.  If the namespaces or types can't be found in scope (in the referenced assemblies), then
            // UnresolvedRefs will be used.  Manually created code is usually created with resolved references, and so needs no
            // resolving, but UnresolvedRefs can also be used if desired.

            // Manually created objects will be default formatted - there's no reason to worry about braces, parentheses, commas,
            // semi-colons, newlines, whitespace, or anything else that is purely syntax-related and not semantic.  However, the
            // default formatting can be overridden using various properties, such as NewLines, HasBraces, HasParens.  You may
            // use '.IsFirstOnLine = true' to force an object to start on a new line (or '.NewLines = 1'), or '.IsSingleLine = true'
            // to force an Expression or Statement to be formatted on a single line.

            // For more examples of manual code creation, see ManualTests.GenerateFullTest() in the Nova.Test solution, which
            // generates a replica of the related FullTest.cs file, which includes most C# language features.

            // Save the entire solution, including all projects and files
            solution.SaveAll();
            Log.WriteLine("Code generated and saved.");

            // You may also save a single project and all of its files using 'project.SaveAll()', or you may save just an individual
            // solution, project, or file by calling '.Save()' on it.  Also, '.SaveAs(string)' methods are provided to save under
            // a different file name.
        }

        /// <summary>
        /// Generate a code fragment, and convert it to a string.
        /// </summary>
        public static void GenerateCodeFragment()
        {
            // Any code object can be created without a parent, and with whatever children are desired.  For example, you might
            // create a ClassDecl or a MethodDecl, or you can create a BlockDecl (a block of statements delimited by braces, as
            // shown below), or you can create an individual Statement or Expression of any kind.
            LocalDecl sum = new LocalDecl("sum", typeof(int), 0);
            LocalDecl flag = new LocalDecl("flag", typeof(bool), false);
            flag.EOLComment = "some flag";
            LocalDecl i = new LocalDecl("i", typeof(int), 1);
            For @for = new For(i, new LessThan(i, 10), new Increment(i),
                new Block(new AddAssign(sum, i), new Assignment(flag, true)));
            @for.Comment = "calculate a sum";
            BlockDecl block = new BlockDecl(sum, flag, @for);

            // When a named code object (such as 'i' above), is used where an Expression is expected, there are implicit conversion
            // operators which create SymbolicRefs (in this case, a LocalRef), avoiding the need to call '.CreateRef()' each time.
            // Likewise, Types (such as 'typeof(int)') used for Expressions are implicitly converted to TypeRefs (avoiding the need to
            // use 'new TypeRef(typeof(int))', and literals (such as '0' or 'false') are implicitly converted to Literals (avoiding
            // the need to use 'new Literal(0)' or 'new Literal(false)'.

            // Only Solution, Project, and CodeUnit objects implement the IFile interface and are mapped to files.  For all other code
            // objects, there is no '.Save()' method.  However, you may convert any code object to a string using 'AsString()'
            // (the 'ToString()' method generates only a 'description' instead of the full object, because otherwise it would cause
            // performance issues when working in the debugger if all objects rendered all of their child objects).
            Log.WriteLine(block.AsString());
        }

        #endregion

        #region /* FINDING CODE OBJECTS EXAMPLES */

        /// <summary>
        /// Finding code objects in a particular scope.
        /// </summary>
        public static void FindCodeObjects()
        {
            // Various 'Find' methods are provided on various CodeDOM types to find immediate child code objects at a particular
            // point in a code tree, examples of which are shown below.

            Solution solution = Solution.Load("Nova.Examples.sln");
            if (solution != null)
            {
                // Find a Project in a Solution by name
                Project project = solution.FindProject("Nova.Examples");
                Log.WriteLine(project != null ? "Found " + project : "ERROR - Project not found");
                if (project != null)
                {
                    // Find a CodeUnit in a Project by name
                    CodeUnit codeUnit = project.FindCodeUnit("Program.cs");
                    Log.WriteLine(codeUnit != null ? "Found " + codeUnit : "ERROR - CodeUnit not found");

                    // Find a Namespace in a Project by full name
                    Namespace namespace_Examples = project.FindNamespace("Nova.Examples");
                    Log.WriteLine(namespace_Examples != null ? "Found " + namespace_Examples : "ERROR - Namespace not found");
                    if (namespace_Examples != null)
                    {
                        // Find a type in a Project as a TypeRef (to a TypeDecl or TypeDefinition or Type) when
                        // you don't know if the type is external or not.
                        SymbolicRef typeRef_Console = project.FindRef("System.Console") as TypeRef;  // Treat UnresolvedRef as null
                        Log.WriteLine(typeRef_Console != null ? "Found " + typeRef_Console : "ERROR - TypeRef not found");

                        // Find a TypeDecl in a Project when you know the type is local (as opposed to external).
                        TypeDecl typeDecl_Program1 = project.Find("Nova.Examples.Program") as TypeDecl;
                        Log.WriteLine(typeDecl_Program1 != null ? "Found " + typeDecl_Program1 : "ERROR - TypeDecl not found");

                        // Find a TypeDefinition (if using Mono Cecil - the default) or a Type (if using Reflection) in a Project
                        // when you know the type is external.
                        if (ApplicationContext.UseMonoCecilLoads)
                        {
                            TypeDefinition typeDefinition = project.Find("System.Console") as TypeDefinition;
                            Log.WriteLine(typeDefinition != null ? "Found TypeDefinition: " + typeDefinition : "ERROR - TypeDefinition not found");
                        }
                        else
                        {
                            Type type = project.Find("System.Console") as Type;
                            Log.WriteLine(type != null ? "Found Type: " + type : "ERROR - Type not found");
                        }

                        // Find a type in a Namespace as a TypeRef
                        TypeRef typeRef_Program = TypeRef.Find(namespace_Examples, "Program") as TypeRef;
                        Log.WriteLine(typeRef_Program != null ? "Found " + typeRef_Program : "ERROR - TypeRef not found");

                        // Find a TypeDecl in a Namespace
                        TypeDecl typeDecl_Program = namespace_Examples.Find("Program") as TypeDecl;
                        Log.WriteLine(typeDecl_Program != null ? "Found " + typeDecl_Program : "ERROR - TypeDecl not found");
                        if (typeDecl_Program != null)
                        {
                            // Find a MethodDecl in a TypeDecl by name
                            MethodDecl methodDecl = typeDecl_Program.FindFirst<MethodDecl>("FindCodeObjects");
                            Log.WriteLine(methodDecl != null ? "Found " + methodDecl : "ERROR - MethodDecl not found");
                            if (methodDecl != null)
                            {
                                // Find a LocalDecl in a MethodDecl by name
                                LocalDecl localDecl = methodDecl.FindFirst<LocalDecl>("solution");
                                Log.WriteLine(localDecl != null ? "Found " + localDecl : "ERROR - LocalDecl not found");
                                if (localDecl != null)
                                {
                                    // Find the Parent MethodDecl of a code object
                                    CodeObject parentMethod = localDecl.FindParentMethod();
                                    Log.WriteLine(parentMethod != null ? "Found " + parentMethod : "ERROR - parent Method not found");

                                    // Find the Parent NamespaceDecl of a code object
                                    NamespaceDecl parentNamespaceDecl = localDecl.FindParent<NamespaceDecl>();
                                    Log.WriteLine(parentNamespaceDecl != null ? "Found " + parentNamespaceDecl : "ERROR - parent NamespaceDecl not found");
                                }

                                // Find the first 'if' statement in a MethodDecl
                                If @if = methodDecl.FindFirst<If>();
                                Log.WriteLine(@if != null ? "Found " + @if : "ERROR - If statement not found");
                                if (@if != null)
                                {
                                    // Find all child 'if' statements in the body of a parent 'if' statement
                                    foreach (CodeObject statement in @if.Body)
                                    {
                                        if (statement is If)
                                            Log.WriteLine("Found child " + statement);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Helper methods are provided on various SymbolicRef types to find types when manually generating code
            // which conveniently return an UnresolvedRef instead of null if the item isn't found.  They can also
            // be passed an UnresolvedRef as input, so the output of one call can be passed as the input of another
            // without any checks for errors.  For examples, see ManualTests.GenerateFullTest().
            // These methods include the following signature patterns:
            //
            // Get a reference to a type or namespace in the current project instance:
            //     SymbolicRef Project.FindRef(string name)
            //
            // Find a type in a namespace or parent type, where NorT is a Namespace or Type/TypeDecl, or an Alias to one
            //     SymbolicRef TypeRef.Find(NorT type, string name)
            //
            // Find a member of a type, where TYPE is a Type, TypeDecl, TypeRefBase, or type Alias:
            //     SymbolicRef MethodRef.Find(TYPE type, string name)
            //     SymbolicRef ConstructorRef.Find(TYPE type, params TypeRefBase[] parameterTypes)
            //     SymbolicRef PropertyRef.Find(TYPE type, string name)
            //     SymbolicRef FieldRef.Find(TYPE type, string name)
            //
            // Find a parameter of a method, where METHOD is a MethodInfo or MethodDeclBase
            //     SymbolicRef ParameterRef.Find(METHOD method, string name)
        }

        #endregion

        #region /* CODE MODIFICATION EXAMPLES */

        /// <summary>
        /// Modify code objects.
        /// </summary>
        public static void ModifyCodeObjects()
        {
            // Renaming a named code object is trivial - just assign a new name, and all resolved references will
            // automatically make use of the new name (for name changes, it's obviously very important that all
            // symbolic references are resolved first).  Code objects have various other properties which can also
            // be directly assigned.  Some code objects have direct children code objects (such as the various parts
            // of a For loop) which can be directly assigned to LocalDecls or Expressions.  Many (such as all
            // BlockStatements) have a Body which is a collection of child code objects which can be manipulated
            // using methods such as Add(), Remove(), and Insert().

            // Here are some examples using a small code fragment:
            const string fragment =
                "/// <see cref=\"OldClass\"/>\n"
                    + "class OldClass\n"
                    + "{\n"
                    + "    public OldClass(int arg)\n"
                    + "    { }\n"
                    + "    \n"
                    + "    private void ToBeDeletedMethod()\n"
                    + "    { }\n"
                    + "    \n"
                    + "    /// <param name=\"oldArg\">The argument.</param>\n"
                    + "    private void Method(int oldArg = -1)\n"
                    + "    {\n"
                    + "        OldClass x = new OldClass(oldArg);\n"
                    + "        for (int i = oldArg; i < 10; ++i)\n"
                    + "            x.Method(oldArg);\n"
                    + "    }\n"
                    + "}\n";
            CodeUnit codeUnit = CodeUnit.LoadFragment(fragment, "Fragment");
            if (codeUnit != null)
            {
                ClassDecl classDecl = codeUnit.FindFirst<ClassDecl>("OldClass");
                if (classDecl != null)
                {
                    // Rename 'OldClass' to 'NewClass' and change it to 'public'
                    classDecl.Name = "NewClass";
                    classDecl.IsPublic = true;

                    MethodDecl methodDecl = classDecl.FindFirst<MethodDecl>("Method");
                    if (methodDecl != null)
                    {
                        // Change 'Method' to 'public', and change its return type from 'void' to 'int'
                        methodDecl.IsPublic = true;
                        methodDecl.ReturnType = typeof(int);

                        // Rename the parameter of 'Method' from 'oldArg' to 'newArg', change its type from 'int' to 'long',
                        // and change its initialization from '-1' to '0'.
                        ParameterDecl parameterDecl = methodDecl.Parameters[0];
                        parameterDecl.Name = "newArg";
                        parameterDecl.Type = typeof(long);
                        parameterDecl.Initialization = 0;
                    }

                    // Insert a new method after the constructor
                    classDecl.Insert(1, new MethodDecl("InsertedMethod", typeof(void)));

                    // Remove a method
                    MethodDecl toBeDeleted = classDecl.FindFirst<MethodDecl>("ToBeDeletedMethod");
                    if (toBeDeleted != null)
                        classDecl.Remove(toBeDeleted);
                }

                // Insert an enum into the CodeUnit before the class
                EnumDecl enumDecl = new EnumDecl("Enum");
                enumDecl.Add(new EnumMemberDecl("One"), new EnumMemberDecl("Two"));
                codeUnit.Insert(0, enumDecl);

                // Display the modified code
                Log.WriteLine(codeUnit.AsString());
            }
        }

        #endregion

        #region /* LINQ EXAMPLES */

#if !USING_NOVA_2  // Skip if loading with Nova 2.x, since it doesn't support LINQ

        /// <summary>
        /// LINQ queries on code objects in a particular local scope.
        /// </summary>
        public static void LinqQueriesLocal()
        {
            // LINQ queries can be performed on any collection, and therefore on the immediate child code objects at a
            // particular point in a code tree using any of the various child object collections.  Examples are shown
            // here both in SQL syntax and also the code alternative.  Global queries are shown in the next example.

            Solution solution = Solution.Load("Nova.Examples.sln");
            if (solution != null)
            {
                // Use LINQ to find project(s) in a solution
                var examplesProject = (from project in solution.Projects
                                       where project.Name == "Nova.Examples"
                                       select project).FirstOrDefault();
                var examplesProject2 = solution.Projects.FirstOrDefault(project => project.Name == "Nova.Examples");
                if (examplesProject != null)
                {
                    Log.WriteLine("Found " + examplesProject);

                    // Use LINQ to find CodeUnit(s) in a project
                    var programFile = (from codeUnit in examplesProject.CodeUnits
                                       where codeUnit.Name == "Program.cs"
                                       select codeUnit).FirstOrDefault();
                    var programFile2 = examplesProject.CodeUnits.FirstOrDefault(codeUnit => codeUnit.Name == "Program.cs");
                    if (programFile != null)
                    {
                        Log.WriteLine("Found " + programFile);

                        // Use LINQ to find ClassDecl(s) in a CodeUnit.  Note that TypeDecls in CodeUnits can be nested
                        // inside one or more NamespaceDecls - the GetTypeDecls(true) method will enumerate all of them.
                        // You can also use 'project.Find()' or '@namespace.Find()' as shown in the FindCodeObjects() example.
                        var programClass = (from classDecl in programFile.GetTypeDecls(true).OfType<ClassDecl>()
                                            where classDecl.Name == "Program"
                                            select classDecl).FirstOrDefault();
                        var programClass2 = programFile.GetTypeDecls(true).OfType<ClassDecl>().FirstOrDefault(classDecl => classDecl.Name == "Program");
                        if (programClass != null)
                        {
                            Log.WriteLine("Found " + programClass);

                            // Use LINQ to find all static MethodDecls in a ClassDecl
                            var methods = from methodDecl in programClass.Body.OfType<MethodDecl>()
                                          where methodDecl.IsStatic
                                          select methodDecl;
                            var methods2 = programClass.Body.OfType<MethodDecl>().Where(methodDecl => methodDecl.IsStatic);
                            Log.WriteLine("Found " + methods.Count() + " static MethodDecls in " + programClass);

                            var methodMain = methods.FirstOrDefault(method => method.Name == "Main");
                            if (methodMain != null)
                            {
                                // Use LINQ to find all top-level Statements in a MethodDecl
                                var statements = from statement in methodMain.Body.OfType<Statement>()
                                                 select statement;
                                var statements2 = methodMain.Body.OfType<Statement>();
                                Log.WriteLine("Found " + statements.Count() + " top-level Statements in " + methodMain);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// LINQ queries on code objects at a global scope.
        /// </summary>
        public static void LinqQueriesGlobal()
        {
            // LINQ queries can be performed across nested collections by chaining 'from' clauses and/or with the use of
            // custom enumerators which recursively traverse trees of collections.  Such techniques make it easy to query
            // code as if it were in a database, searching for patterns of code or calculating advanced code metrics.
            // The examples shown here simply select all objects of a particular type, but various criteria can be applied
            // (such as with a 'where' clause) to select whatever sub-set of the objects is desired.

            Solution solution = Solution.Load("Nova.Examples.sln");
            if (solution != null)
            {
                // Query all files (CodeUnits) in an entire Solution
                var allFiles = from project in solution.Projects
                               from codeUnit in project.CodeUnits
                               select codeUnit;
                Log.WriteLine("Found " + allFiles.Count() + " total files in the entire Solution");

                // Query all Namespaces in an entire Solution (declared or imported from referenced assemblies)
                var allNamespaces = from project in solution.Projects
                                    from @namespace in project.GlobalNamespace.GetAllChildren<Namespace>()
                                    select @namespace;
                Log.WriteLine("Found " + allNamespaces.Count() + " Namespaces (declared or imported) in the Solution");

                // Note that each Project maintains its own Namespaces because the Types and TypeDecls in the Namespaces depend
                // upon the referenced assemblies and Projects, which can be different for each Project.  So, querying at the
                // Solution level can result in duplicate Namespaces and/or Types when multiple Projects exist, athough TypeDecls
                // will generally not be duplicated (but could be if the same source file is included in more than one Project).

                // Query all types in an entire Solution (imported from referenced assemblies).
                // Varies if using Mono Cecil vs Reflection vs either of them.
                var allTypes = from project in solution.Projects
                               from type in project.GlobalNamespace.GetAllChildren<object>()  // Or TypeDefinition or Type
                               where type is TypeDefinition || type is Type  // Omit if using TypeDefinition or Type above
                               select type;
                Log.WriteLine("Found " + allTypes.Count() + " imported Types in the Solution");

                // Query all TypeDecls in an entire Solution
                var allTypeDecls = from project in solution.Projects
                                   from typeDecl in project.GlobalNamespace.GetAllChildren<TypeDecl>()
                                   select typeDecl;
                Log.WriteLine("Found " + allTypeDecls.Count() + " TypeDecls in the Solution");

                Project exampleProject = solution.FindProject("Nova.Examples");
                if (exampleProject != null)
                {
                    // Query all Namespaces in a Project (declared or imported from referenced assemblies and Projects)
                    var namespaces = from @namespace in exampleProject.GlobalNamespace.GetAllChildren<Namespace>()
                                     select @namespace;
                    Log.WriteLine("Found " + namespaces.Count() + " Namespaces (declared or imported) in the Project");

                    // Query all Types in a Project (imported from referenced assemblies).
                    // Varies if using Mono Cecil vs Reflection vs either of them.
                    var types = from type in exampleProject.GlobalNamespace.GetAllChildren<object>()  // Or TypeDefinition or Type
                                where type is TypeDefinition || type is Type  // Omit if using TypeDefinition or Type above
                                select type;
                    Log.WriteLine("Found " + types.Count() + " imported Types in the Project");

                    // Query all TypeDecls in a Project (declared or imported from referenced Projects)
                    var typeDecls = from typeDecl in exampleProject.GlobalNamespace.GetAllChildren<TypeDecl>()
                                    select typeDecl;
                    Log.WriteLine("Found " + typeDecls.Count() + " TypeDecls (declared or imported) in the Project");

                    // Query all TypeDecls that are declared in a Project (excluding those imported from other Projects)
                    var declaredTypeDecls = from typeDecl in exampleProject.GetAllDeclaredTypeDecls()
                                            select typeDecl;
                    Log.WriteLine("Found " + declaredTypeDecls.Count() + " TypeDecls declared in the Project");
                }
            }
        }

#endif

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Software Developer (Senior)
United States United States
I've been writing software since the late 70's, currently focusing mainly on C#.NET. I also like to travel around the world, and I own a Chocolate Factory (sadly, none of my employees are oompa loompas).

Comments and Discussions