Monday, April 20, 2009

Using CodeSmith.CodeFileParser

The CodeFileParser

In CodeSmith v5.1 we are introducing a new feature: the CodeFileParser!

The Inspiration

We are always wondering, how can we make CodeSmith better? We noticed that more and more cars have the 'flex fuel' logo on them these days; so we asked ourselves, "How can we learn from that? How can we make CodeSmith's fuel more flexible?" Well, CodeSmith is fueled by metadata, so how can we make metadata more flexible? ...and then it came to us: add more metadata!

The Feature

The CodeFileParser will make it easier for CodeSmith templates to use code files as their metadata source. It is a simple class that takes in a file path, or even a content string, parses the class, and returns an easy to walk/search DOM object. So think about that for a moment; this means that you can generate code, *dramatic pause*, from code.

I'll just let that sink in...
...take your time...
...pretty sweet, huh?

The Implementation

The Class

CodeSmith.Engine now contains the CodeFileParser class. It is capable of parsing both C# and VB code. As mentioned above, it is capable of taking in a file path or a content string, and it will take care of reading the file (or string) and parsing the contents for you. Under the hood the CodeFileParser uses the public NRefactory libraries created by the great team over at SharpDevelop.

// There are overloads that don't require basePath or parseMethodBodies.
public CodeFileParser(string fileName, string basePath, bool parseMethodBodies)
// There are overloads that don't require parseMethodBodies.
public CodeFileParser(string source, SupportedLanguage language, bool parseMethodBodies)

The Selection Methods

Most of the methods in NRefactory return position information in the form of Location objects, which, while very descriptive, are not the easiest thing to use when trying to take substrings or selections from the existing code. Because this can be very important when using the object DOM to assist with code generation, we have added several methods to assist with getting substrings and selections; these methods take in Location objects and return strings.

public string GetSectionFromStart(Location end)
public string GetSectionToEnd(Location start)
public string GetSection(Location start, Location end)

The CodeDomCompilationUnit

To quick and easily walk the DOM, the CodeFileParser exposes a (lazy loaded) property that returns System.CodeDom.CodeCompileUnit object. This is a standard .NET object that contains a complete code graph; this object is the quickest and easiest way to traverse your metadata. For more information about the CodeCompileUnit, please check out MSDN article.

The Visitor

When more advanced or customized information is required, the CodeFileParser exposes the CompilationUnit object, which is capable of taking in a visitor object to traverse the DOM and bring back specific data. This is an NRefactory feature, and it only requires that your visitor object implement the AbstractAstVisitor class.

The Example

The We're Already Using It!

We are already using the CodeFileParser in CodeSmith and our Plinqo templates! In CodeSmith we have implemented the CodeFileParser in our InsertClassMergeStrategy; it allows us to parse the existing code file and determine where we need to insert our new content. In Plinqo we use the CodeFileParser to assist with our MetaData class merge; it allows us to make a map of all the properties in that class and then preserve their attributes during regeneration.

The Template Code

<%@ CodeTemplate Language="C#" TargetLanguage="Text" Debug="False" CompilerVersion="v3.5" %>
<%@ Property Category="2.Class" Name="TheFile" Type="CodeFileParser" Optional="False" %>
<%@ Assembly Name="CodeSmith.CodeParser" %>
<%@ Import Namespace="System.CodeDom" %>

<%  foreach(CodeNamespace n in TheFile.CodeDomCompilationUnit.Namespaces) { %>
Namespace: <%= n.Name %>
<%      foreach(CodeTypeDeclaration t in n.Types) { %>
    Type: <%= t.Name %>
<%          foreach(CodeTypeMember m in t.Members) { %>
        Member: <%= m.Name %>
<%          } %>
<%      } %>
<%  } %>

Small footnote, thanks to Seinfeld for inspiring my section title names in this blog post.

Real Time Web Analytics