SubMain - CodeIt.Right The First Time!

/Community

Support Community for SubMain Products
 Home Products Services Download Purchase Support
in Search
 
Home Forums Blogs Tutorials/CIR Tutorials/GD Downloads
Welcome to SubMain Community Sign in | Join | Help

SubMain Blog

GhostDoc - Customizing Generated Method Header Comments

Last month, I wrote a post introducing you to T4 templates.  Near the end, I included a mention of GhostDoc's use of T4 templates in automatically generating code comments.  Today, I'd like to expand on that.

To recap very briefly, recall that Ghost Doc allows you to generate things like method header comments.  I recommend that, in most cases, you let it do its thing.  It does a good job.  But sometimes, you might have occasion to want to tweak the result.  And you can do that by making use of T4 Templates.

Documenting Chess TDD

To demonstrate, let's revisit my trusty toy code base, Chess TDD.  Because I put this code together for instructional purposes and not to release as a product, it has no method header comments for IntelliSense's benefit.  This makes it the perfect candidate for a demonstration.

If I had released this as a library, I'd have started the documentation with the Board class.  Most of the client interaction would happen via Board, so let's document that.  It offers you a constructor and a bunch of semantics around placing and moving pieces.  Let's document the conceptually simple MovePiece method.

public void MovePiece(BoardCoordinate
origin, BoardCoordinate destination) { VerifyCoordinatesOrThrow(origin, destination); var pieceToMove = GetPiece(origin);
AddPiece(pieceToMove, destination); RemovePiece(origin); pieceToMove.HasMoved = true;
ReconcileEnPassant(origin, destination, pieceToMove); }

To add documentation to this method, I simply right click it and, from the GhostDoc context menu, select "Document This."  Alternatively, I can use the keyboard shortcut Ctrl-Shift-D.  Either option yields the following result.

/// <summary> /// Moves
the piece. /// </summary> /// <param
name="origin">The origin.</param> /// <param
name="destination">The destination.</param> public void MovePiece(BoardCoordinate
origin, BoardCoordinate destination) { VerifyCoordinatesOrThrow(origin, destination); var pieceToMove = GetPiece(origin);
AddPiece(pieceToMove, destination); RemovePiece(origin); pieceToMove.HasMoved = true;
ReconcileEnPassant(origin, destination, pieceToMove); }

Let's Make a Tiny Tweak

Alright, much better!  If I scrutinize the comment, I can imagine what an IntelliSense-using client will see.  My parameter naming makes this conceptually simple to understand, so the IntelliSense will tell the user that the first parameter represents the origin square and the second parameter the destination.

But let's say that as I look at this, I find myself wanting to pick at a nit.  I don't care for the summary taking up three lines -- I want to condense it to one.  How might I do that?

Well, let's crack open the T4 template for generating a method header.  Recall that you do this in Visual Studio by selecting Tools->Ghost Doc->Options, and picking "Rules" from the options pane.

blog-intro-to-t4-templates-part2-1

If you double click on "Method Template", as highlighted above, you will see an "Edit Rule" Window.  The first few lines of code in that window look like this.

<#@ template language="C#" #> <# CodeElement
codeElement = Context.CurrentCodeElement; #> ///
<summary> ///<# GenerateSummaryText(); #> ///
</summary> <# if(codeElement.HasTypeParameters)
{ for(int i = 0;
i < codeElement.TypeParameters.Length;
i++)
{ TypeParameter typeParameter = codeElement.TypeParametersIdea; #>

Hmmm.  I cannot count myself an expert in T4 templates, per se, but I think I have an idea.  Let's put that call to GenerateSummaryText() inline between the summary tags.  Like this:

<#@ template language="C#" #> <# CodeElement
codeElement = Context.CurrentCodeElement; #> ///
<summary><# GenerateSummaryText(); #></summary>

That should do it, right?  Let's regenerate the comment and see what it looks like.  This results in the following.

/// <summary>Moves
the piece. /// </summary> /// <param
name="origin">The origin.</param> /// <param
name="destination">The destination.</param> public void MovePiece(BoardCoordinate
origin, BoardCoordinate destination) { VerifyCoordinatesOrThrow(origin, destination); var pieceToMove = GetPiece(origin);
AddPiece(pieceToMove, destination); RemovePiece(origin); pieceToMove.HasMoved = true;
ReconcileEnPassant(origin, destination, pieceToMove); }

Uh, oh.  It made a difference, but somehow we only got halfway there.  Why might that be?

Diving Deeper

To understand, we need to look at the template in a bit more detail.  The template itself has everything on one line, and yet we see a newline in there somehow.  Could GenerateTextSummary cause this, somehow?  Let's scroll down to look at it.  Since this method has a lot of code, here are the first few lines only.

private void GenerateSummaryText()
{ if(Context.HasExistingTagText("summary"))
{ this.WriteLine(Context.GetExistingTagText("summary"));
} else if(IsAsyncMethod())
{ this.WriteLine(Context.ExecMacro("$(MethodName.Words.ExceptLast)") + " as
an asynchronous operation.");
} else if(IsMainMethod())
{ this.WriteLine("Defines
the entry point of the application.");
} }

Aha!  Notice that we're calling WriteLine.  What if we did a find and replace to change all of those to just Write?  Let's try.  (To do more serious operations like this, you will want to copy the text out of the editor and into your favorite text editor in order to get more operations).

Once you have replaced all instances of WriteLine with Write in the template, here is the new result.

/// <summary>Moves
the piece.</summary> /// <param
name="origin">The origin.</param> /// <param
name="destination">The destination.</param> public void MovePiece(BoardCoordinate
origin, BoardCoordinate destination) { VerifyCoordinatesOrThrow(origin, destination); var pieceToMove = GetPiece(origin);
AddPiece(pieceToMove, destination); RemovePiece(origin); pieceToMove.HasMoved = true;
ReconcileEnPassant(origin, destination, pieceToMove); }

Success!

Validation

As you play with this, you might have noticed a "Validate" button in the rule editor.  Use this liberally!  This button will trigger a parsing of the template and provide you with feedback as to validity.  The last thing you want to do is work in here for many iterations and wind up with no idea what you broke and when.

When working with these templates, think of this as equivalent to compiling.  You wouldn't want to sit for 20 minutes writing code with no feedback as to whether it builds or not.  So don't do it with these templates.

The Power at Your Disposal

I'll wrap here for this particular lesson, but understand that we have barely scratched the surface of what you can do.  In this post, we just changed a bit of the formatting to suit a whim I had.  But you can really dive into ways of reasoning about and documenting the code if you so choose.

Stay tuned for future posts on more advanced tips and tricks with your comment templates.

Learn more about how GhostDoc can help simplify your XML Comments, produce and maintain quality help documentation.

About the Author

Erik Dietrich

I'm a passionate software developer and active blogger. Read about me at my site. View all posts by Erik Dietrich

Published Tuesday, January 3, 2017 2:47 AM by SubMain.Blog()

Comments

No Comments
Anonymous comments are disabled
    

This Blog

Syndication

 
     
 
Home |  Products |  Services |  Download |  Purchase |  Support |  Community |  About Us |