Playing Catch-Up
This unfortunately left us with a lot of catch-up work. We needed to fill in and correct thousands of missing comments. While it would have been the most valuable to add thoughtful defined explanations in those places it also would have been the most costly - and it wasn't in our project budget to take on that cost. As a result I adapted a Visual Studio macro with some code-DOM traversing material borrowed from TeamReview in order to leverage GhostDoc to automatically fill in missing comments and fix some comment structure for an entire Visual Studio solution. This allowed us to take on the endeavor of changing our S&BPs, being able to enforce them for the better, and allowed us to move forward without immediately paying for Technical Debt due to the change in S&BPs.
Again, GhostDoc comments are meant to get us over the hurdle of not being able to enforce a coding standard. I know as well as you that auto-generated stuff isn't very valuable, but without it we wouldn't be able to approach this endeavor.
Before you run the macro
Be sure your code compiles and that you have the entire solution checked out from source control. Otherwise you'll get lots and lots of prompts, potentially two for every file.
Below is the code. The macro is named after the Winston Wolf character in the movie Pulp Fiction.
The Code
Option Explicit Off
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Public Module TheWolf
Public Sub CleanUpSolution()
IterateFiles()
End Sub
Private Sub IterateFiles()
Dim project As EnvDTE.Project
Dim projects As EnvDTE.Projects
Dim window As Window
Dim target As Object
DTE.ExecuteCommand("View.SolutionExplorer")
window = DTE.Windows.Item(Constants.vsWindowKindCommandWindow)
projects = DTE.Solution.Projects
If projects.Count = 0 Then
Exit Sub
End If
For Each project In projects
If (Not project.ProjectItems Is Nothing) Then
CleanUpProject(project.ProjectItems())
End If
Next
'RebuildSolution()
End Sub
Private Sub CleanUpProject(ByVal items)
Dim file As ProjectItem
For Each file In items
If file.Name.EndsWith(".cs") OrElse file.Name.EndsWith(".vb") Then
ActivateFile(file)
RemoveAndSortUsings()
FormatDocument()
DocumentCode(file)
End If
'Handle folders within a project
If Not file.ProjectItems() Is Nothing Then
If file.ProjectItems.Count > 0 Then
CleanUpProject(file.ProjectItems())
End If
End If
SaveAll()
CloseAll()
Next
End Sub
Private Sub RebuildSolution()
DTE.ExecuteCommand("Build.RebuildSolution")
End Sub
Private Sub SaveAll()
Try
DTE.ExecuteCommand("File.SaveAll")
Catch ex As Exception
End Try
End Sub
Private Sub CloseAll()
Try
DTE.ExecuteCommand("File.CloseAllButThis")
DTE.ExecuteCommand("File.Close")
Catch ex As Exception
End Try
End Sub
Private Sub ActivateFile(ByVal file As ProjectItem)
file.Open()
If (Not file.Document Is Nothing) Then
file.Document.Activate()
End If
Try
DTE.ExecuteCommand("View.ViewCode")
Catch ex As Exception
'do nothing - it's probably already viewable
End Try
End Sub
Private Sub RemoveAndSortUsings()
Try
DTE.ExecuteCommand("Edit.RemoveAndSort")
Catch ex As Exception
'does not work on VB code
End Try
End Sub
Private Sub FormatDocument()
DTE.ExecuteCommand("Edit.FormatDocument")
End Sub
Private Sub DocumentCode(ByVal file As ProjectItem)
Dim element As EnvDTE.CodeElement
Dim document As EnvDTE.TextDocument
document = CType(file.Document.Object(""), EnvDTE.TextDocument)
If (document Is Nothing) Then
Return
End If
DocumentElements(document, file.FileCodeModel.CodeElements)
End Sub
Private Sub DocumentElement(ByVal document As EnvDTE.TextDocument, ByVal element As EnvDTE.CodeElement)
Dim startPoint As EnvDTE.EditPoint
document.Selection.GotoLine(element.StartPoint.Line, True)
startPoint = document.CreateEditPoint(document.Selection. ActivePoint)
document.Selection.MoveToPoint(startPoint, True)
DTE.ExecuteCommand("Weigelt.GhostDoc.AddIn.DocumentThis")
End Sub
Private Sub DocumentElements(ByVal document As EnvDTE.TextDocument, ByVal elements As EnvDTE.CodeElements)
Try
For Each element In elements
Select Case element.Kind
Case vsCMElement.vsCMElementFunction
Dim func As EnvDTE.CodeFunction
func = CType(element, EnvDTE.CodeFunction)
If (func.Access <> vsCMAccess.vsCMAccessPrivate And func.Access <> vsCMAccess.vsCMAccessProject) Then
DocumentElement(document, element)
End If
DocumentElements(document, func.Children)
Case vsCMElement.vsCMElementProperty
Dim prop As EnvDTE.CodeProperty
prop = CType(element, EnvDTE.CodeProperty)
If (prop.Access <> vsCMAccess.vsCMAccessPrivate And prop.Access <> vsCMAccess.vsCMAccessProject) Then
DocumentElement(document, element)
End If
DocumentElements(document, prop.Children)
Case vsCMElement.vsCMElementEvent
Dim evt As EnvDTE80.CodeEvent
evt = CType(element, EnvDTE80.CodeEvent)
If (evt.Access <> vsCMAccess.vsCMAccessPrivate And evt.Access <> vsCMAccess.vsCMAccessProject) Then
DocumentElement(document, element)
End If
DocumentElements(document, evt.Children)
Case vsCMElement.vsCMElementClass
Dim cls As EnvDTE.CodeClass
cls = CType(element, EnvDTE.CodeClass)
If (cls.Access <> vsCMAccess.vsCMAccessPrivate And cls.Access <> vsCMAccess.vsCMAccessProject) Then
DocumentElement(document, element)
End If
DocumentElements(document, cls.Children)
Case vsCMElement.vsCMElementStruct
Dim strct As EnvDTE.CodeStruct
strct = CType(element, EnvDTE.CodeStruct)
If (strct.Access <> vsCMAccess.vsCMAccessPrivate And strct.Access <> vsCMAccess.vsCMAccessProject) Then
DocumentElement(document, element)
End If
DocumentElements(document, strct.Children)
Case vsCMElement.vsCMElementDelegate
Dim dlg As EnvDTE.CodeDelegate
dlg = CType(element, EnvDTE.CodeDelegate)
If (dlg.Access <> vsCMAccess.vsCMAccessPrivate And dlg.Access <> vsCMAccess.vsCMAccessProject) Then
DocumentElement(document, element)
End If
DocumentElements(document, dlg.Children)
Case vsCMElement.vsCMElementEnum
Dim enm As EnvDTE.CodeEnum
enm = CType(element, EnvDTE.CodeEnum)
If (enm.Access <> vsCMAccess.vsCMAccessPrivate And enm.Access <> vsCMAccess.vsCMAccessProject) Then
DocumentElement(document, element)
End If
DocumentElements(document, enm.Children)
Case (vsCMElement.vsCMElementVariable)
Dim var As EnvDTE.CodeVariable
var = CType(element, EnvDTE.CodeVariable)
If (var.Access <> vsCMAccess.vsCMAccessPrivate And var.Access <> vsCMAccess.vsCMAccessProject) Then
DocumentElement(document, element)
End If
Case vsCMElement.vsCMElementNamespace
Dim nmspc As EnvDTE.CodeNamespace
nmspc = CType(element, EnvDTE.CodeNamespace)
DocumentElements(document, nmspc.Children)Case vsCMElement.End SelectvsCMElementInterface
Dim inter As EnvDTE.CodeInterface
inter = CType(element, EnvDTE.CodeInterface)
If (inter.Access <> vsCMAccess.vsCMAccessPrivate And inter.Access <> vsCMAccess.vsCMAccessProject) Then
DocumentElement(document, element)
End If
DocumentElements(document, inter.Children)
Next
Catch ex As Exception
End Try
End Sub
End Module
12 comments:
hello, I updated your code so it will work with VB too, there are just two small changes necessary.
1. in CleanUpProject update the file type selector this:
If file.Name.EndsWith(".cs") OrElse file.Name.EndsWith(".vb") Then
2. add a try catch with no handler around DTE.ExecuteCommand("Edit.RemoveAndSort") this fails in VB. So it can just be ignored. I am sure there is a better way to do this, but it works.
Thanks for the code!
Also, I am wondering why your code is not commented. :)
Gabe - I have updated the sample with your findings. Thanks for taking the time to let me know about them!
I certainly would get docked on comments in a code review for this macro. I felt like the names of methods were pretty explanatory, but you're right, I should have provided some highlights to readers with comments.
JB
JB,
I thought I posted this earlier, but there was one more I found when I was testing.
3. In document Elements we need to add this code because of VB Modules:
Case vsCMElement.vsCMElementModule
Dim cls As EnvDTE.CodeClass
cls = CType(element, EnvDTE.CodeClass)
'we can't document a module ghostdoc doesn't support it
DocumentElements(document, cls.Children)
The modules themselves do not get comments, but the child methods do need them.
Additionally, I modified/added the public methods so I could do a single doc, and some extra code to confirm, cleanUpProject was just refactored because it shares code wiht CleanUpCurrentFile:
Public Sub CleanUpSolution()
If MsgBox("Are you sure you want to clean up the entire solution?", MsgBoxStyle.YesNo + MsgBoxStyle.Exclamation, "Clean Up Solution?") = MsgBoxResult.Yes Then
iterateFiles()
End If
End Sub
Public Sub CleanUpCurrentFile()
cleanupFile(DTE.ActiveDocument.ProjectItem)
End Sub
Private Sub cleanupFile(ByVal file As ProjectItem)
If file.Name.EndsWith(".cs") OrElse file.Name.EndsWith(".vb") Then
activateFile(file)
removeAndSortUsings()
formatDocument()
documentCode(file)
End If
End Sub
Private Sub cleanUpProject(ByVal items)
Dim file As ProjectItem
For Each file In items
cleanupFile(file)
'Handle folders within a project
If file.ProjectItems() IsNot Nothing Then
If file.ProjectItems.Count > 0 Then
cleanUpProject(file.ProjectItems())
End If
End If
saveAll()
closeAll()
Next
End Sub
Also I was joking about the comments I am just as guilty... or else why would I be interested in automating GhostDoc!
Thanks Again -Gabe
Ha! Sorry, I realize now that my head wasn't in the right place and I completely missed the very obvious humor in your post.
JB
Hello...
This is one post thats very interesting and very very handy...
I have been trying to get this into working but it seems as if the macro is unable to iterate projects contained inside multiple folder stucture.....
I have a 3 level folder structure where the project lies in the 3 level. Is there any way that this can iterate through a large project structure.....
Rajiv,
The easy answer is yes, you can do just about anything with the macro language.
It is just a matter of modifying the code that loops over the project. The best place to start is to put some breakpoints within the macro and see what you can do to iterate the folders you are talking about.
In the cleanupprojects you probably need to make sure you are getting all of the children files, or accounting for folders. At least this is my guess without looking at it.
If you figure anything out let us know. I mainly use this for doing one file at a time. I think it is great!
Gabe
Thank you for your very useful macro. SubMain has recently acquired GhostDoc, and some changes occured along the line. We must modify the macro to work with the current version of GhostDoc (2.5) by editing the macro and replacing DTE.ExecuteCommand("Weigelt.GhostDoc.AddIn.DocumentThis") with DTE.ExecuteCommand("Tools.SubMain.GhostDoc.DocumentThis").
Also came across another issue. When you have a file in the solution without a code model (say a plain text file), the macro crashes. I just added sentinel if statements to the DocumentCode Sub:
'This was here already
If (document Is Nothing) Then
Return
End If
'This is new
If (file.FileCodeModel Is Nothing) Then
Return
End If
If (file.FileCodeModel.CodeElements Is Nothing) Then
Return
End If
'This was here already
DocumentElements(document, file.FileCodeModel.CodeElements)
Thanks a lot for your work. I was already messing up with macros and how to document the whole solution, tried Atomnineer with no luck, than write my own macro to document at least a file (with lots of TextSelection work)..but you solution is perfect.
I have only one big problem - after I ran the macro, after a few seconds, I get the "System call failed" exception (hresult 0x80010100 (RPC_E_SYS_CALL_FAILED). The same exception I got while running my other macro, so it is not an issue with your code, but with VS at all.. I have Visual Studio 2010..
Any one any Idea how to fix it? :)
Good work,
http://weblogs.asp.net/soever/archive/2007/02/20/enumerating-projects-in-a-visual-studio-solution.aspx?CommentPosted=true#commentmessage is an example of how enumerate all projects.
i to have this
I have only one big problem - after I ran the macro, after a few seconds, I get the "System call failed" exception (hresult 0x80010100 (RPC_E_SYS_CALL_FAILED)
Hi, Great post
but I am getting error at EnvDTE80.DTE2.ExecuteCommand(String CommandName, String CommandArgs)
at myGhost.TheWolf.DocumentElement(TextDocument document, CodeElement element) in vsmacros://D%3A/Personal/R%26D/myGhost/myGhost.vsmacros/TheWolf:line 120
Post a Comment