// 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
}
}