At work we've started leveraging
Visual Studio Team System to essentially
automate the checking of Standards and Best Practices (S&BPs). We decided that anything that could be automatically enforced could be deemed part of the S&BPs because it was cost effective. Anything else could be deemed guidance but would not be required. One of the aspects that we desired was being able to effectively use
Sandcastle to generate technical documentation. I personally feel that the most valuable documentation from XML comments comes form the comments on
Acceptance Tests since they define the expectations of the system. To make those XML comments required we created
a shared MSBuild file that treated some of the XML compiler warnings as errors.
Playing Catch-UpThis 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 macroBe 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 CodeOption 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.vsCMElementInterface
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)
End Select
Next
Catch ex As Exception
End Try
End Sub
End Module