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?