I got my ICodeParser implementation!
Note: this entry has moved.
Well, sort of :S. After "reflectoring" quite a while, I found the VsCodeDomGenerator
, VsCodeDomProvider
and VsCodeDomParser
in the Microsoft.VisualStudio.dll
assembly. After trying to create those classes through reflection, using all sorts of hacks and ugly tricks, I was about to give up, as everything failed with exceptions, as it expected a DTE ProjectItem and the like.
But then, I remembered that if you develop a custom tool using the MS provided helper classes, you get a member called CodeProvider
, which is a reference to a valid CodeDomProvider
. One of the cool things of the BaseCodeGeneratorWithSite
is that it automatically "knows" which provider to give you depending on the current project type, so don't need to worry about file extensions and the like. Unlike what you may think, the base implementation doesn't "switch" according to the file extension, but does a more "complicate" operation: being a "sited" component (as the class name suggests), it can query for services from the IDE like any "sited" component would, through the GetService
method, and that's what it does:
protected virtual CodeDomProvider CodeProvider {
get {
if (codeDomProvider == null) {
IVSMDCodeDomProvider vsmdCodeDomProvider = (IVSMDCodeDomProvider) GetService(CodeDomServiceGuid);
if (vsmdCodeDomProvider != null) {
codeDomProvider = (CodeDomProvider) vsmdCodeDomProvider.CodeDomProvider;
}
Debug.Assert(codeDomProvider != null, "Get CodeDomProvider Interface failed.");
}
return codeDomProvider;
}
Now I started to get excited!!! It turns out that this member gives you a VsCodeDomProvider
instead of the appropriate VBCodeProvider
or CSharpCodeProvider
, which always return null
from their CreateParser
method. You guessed right, the VsCodeDomProvider
returns an instance of the VsCodeDomParser
!! Now everything I needed was grasp it, get the project item selected, parse it, and play with the CodeDom before writing it back!!!
Well, that turned out to be harder than it sounds. First, getting the project item. How do you get that from inside a custom tool?!?! No help whatsoever. Add-ins are blessed there, but no luck with custom tools... until after a good time hacking here and there, I found a way, through the marvelous GetService
method:
// Get the item.
EnvDTE.ProjectItem item = base.GetService(typeof(EnvDTE.ProjectItem))
as EnvDTE.ProjectItem;
Now I can get the selected file, open it and parse it:
// Create the parser for the item.
ICodeParser parser = base.CodeProvider.CreateParser();
if (item == null || parser == null)
throw new ArgumentException("This custom tool can only be applied to code items.");
// Parse the file as CodeDom.
using (FileStream fs = new FileStream(item.get_FileNames(0), FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (StreamReader sr = new StreamReader(fs))
{
CodeCompileUnit unit = parser.Parse(sr);
//Play with the CodeDom!!!!
At this point, I had to do some cleaning on the resulting unit because the parser sets the LinePragma
property on every member/class/statement, which results in code full of preprocessor directives such as #ExternalSource
(VB) and #line
(C#). This is just a mater of iterating the CodeCompileUnit
and setting that property to null.
After such a success, I ran to create a project, and two samples for round-tripping VB and C# code, which is only natural if you have a CodeDom at hand. I even submitted it to GotDotNet about a week ago (didn't get online yet, don't know why).
I was all too happy, really... until I saw the code generated from that unit. The parser fails to parse methods, converts array fields into simple (single item) elements, completely loses properties and all custom attributes. As I had done my early tests from an XSD passed through Chris Sells XsdClassesGenerator, or, what is the same, through XSD.EXE, I didn't see these failures early on my testings... :(. Well, but I don't lose my faith that maybe Whidbey will give me a much better CodeProvider, right?