Today I decided to continue work on a Visual Studio macro I started developing on Sunday (I mainly worked on it today though - Sunday was just document reading before heading back to Munich and going out with friends).

The samples from Microsoft simply use the comments from MSDN on functions/methods that are part of a class’s interface implementation.

I ‘documented’ a few of my functions by copying the summary out of MSDN but it’s a tedious job and consequently I decided to try and automate it. Although Visual Basic ‘sucks’ or is a least quite some change from writing C/C++/C# code all the time, the Automation model is very powerful and quite nice to use.

My macro provides two methods:

  • One to add comments to all methods that are part of an interface’s implementation
  • One to add comments to the method the cursor currently resides in

My code currently only adds comments from interface definitions that have been defined outside the current project. It’s pretty nifty in my opinion because usually these interface comments won’t change a lot and thus it’s safe to add them to the source code, while the interfaces one has written themself can still change and the macros can’t track that and/or update the comments afterwards.

However, I’ve added a configuration boolean, so this behavior can be turned off if needed.

My code won’t remove or replace comments, it will just add the comment in front of other comments - if the comment doesn’t already exist - I’ve tried to make it quite safe, so code loss or corruption will be avoided.

I hope this code is helpful. I’m releasing it under the Microsoft Public License. If there are good reasons to use a different license, feel free to tell me so :)

Cheers,
 Andreas

' Andreas 'BlackHC' Kirsch 2008
' released under Microsoft Public License
Imports System
Imports EnvDTE

Public Module CommentImplementedMethods
    ' we usually only want to use external interfaces because it's safer to assume those won't change anytime soon
    Const UseExternalInterfaceCommentsOnly = True

    Private Function GetSummaryFromDocComment(ByVal docComment As String) As String
        Const summaryStartTag = "<summary>"
        Const summaryEndTag = "</summary>"
        Dim summaryIndex = docComment.IndexOf(summaryStartTag)
        If summaryIndex = -1 Then
            Return ""
        End If
        Dim summaryStart = docComment.Substring(summaryIndex + summaryStartTag.Length)
        Return summaryStart.Substring(0, summaryStart.IndexOf(summaryEndTag))
    End Function

    'Private Function GetInterfaceByName(ByRef classObject As CodeClass, ByRef name As String) As CodeInterface
    '    For Each implementedInterface As CodeInterface In classObject.ImplementedInterfaces
    '        If implementedInterface.Name = name Then
    '            Return implementedInterface
    '        End If
    '    Next
    'End Function

    Private Function GetMethod(ByRef interfaceObject As CodeInterface, ByRef methodObject As CodeFunction) As CodeFunction
        Dim prototypeFlags As Int32 = vsCMPrototype.vsCMPrototypeParamTypes Or vsCMPrototype.vsCMPrototypeType
        Dim prototype As String = methodObject.Prototype(prototypeFlags)

        Dim separationIndex = methodObject.Name.IndexOf("."c)
        If separationIndex <> -1 Then
            Dim interfaceName = methodObject.Name.Substring(0, separationIndex)
            If interfaceName <> interfaceObject.Name Then
                Return Nothing
            End If

            Dim realMethodName = methodObject.Name.Substring(separationIndex + 1)
            prototype = prototype.Replace(methodObject.Name, realMethodName)
        End If

        For Each member As CodeElement In interfaceObject.Members
            If member.Kind <> vsCMElement.vsCMElementFunction Then
                Continue For
            End If

            Dim method As CodeFunction = member
            If method.Prototype(prototypeFlags) = prototype Then
                Return method
            End If
        Next

        Return Nothing
    End Function

    Private Function GetInterfaceMethod(ByRef methodObject As CodeFunction) As CodeFunction
        Dim parentClass As CodeClass = methodObject.Parent
        For Each implementedInterface As CodeInterface In parentClass.ImplementedInterfaces
            If implementedInterface.InfoLocation = vsCMInfoLocation.vsCMInfoLocationProject And UseExternalInterfaceCommentsOnly Then
                Continue For
            End If
            Dim matchedMethod = GetMethod(implementedInterface, methodObject)
            If Not IsNothing(matchedMethod) Then
                Return matchedMethod
            End If
        Next
    End Function

    Private Function GetCurrentMethod() As CodeFunction
        Dim sel As TextSelection = _
            CType(DTE.ActiveDocument.Selection, TextSelection)
        Dim pnt As TextPoint = CType(sel.ActivePoint, TextPoint)

        Dim method As CodeFunction = pnt.CodeElement(vsCMElement.vsCMElementFunction)
        Return method
    End Function

    Private Sub AddCommentToMethod(ByRef methodObject As CodeFunction, ByRef newComment As String)
        If methodObject.Comment.IndexOf(newComment) <> -1 Then
            ' This comment has already been added
            Exit Sub
        End If
        methodObject.Comment = newComment & vbNewLine & methodObject.Comment
    End Sub

    Public Sub CommentImplementedMethod()
        Try
            Dim currentMethod = GetCurrentMethod()
            If IsNothing(currentMethod) Then
                MsgBox("Not inside a method scope at the moment!")
                Exit Sub
            End If

            Dim interfaceMethod = GetInterfaceMethod(currentMethod)
            If IsNothing(interfaceMethod) Then
                If UseExternalInterfaceCommentsOnly Then
                    MsgBox("*External* interface method could not be found!")
                Else
                    MsgBox("Interface method could not be found!")
                End If
                Exit Sub
            End If

            Dim newComment = GetSummaryFromDocComment(interfaceMethod.DocComment)
            AddCommentToMethod(currentMethod, newComment)
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
    End Sub

    Private Sub TraverseAndCommentImplementedMethods(ByRef elements As CodeElements)
        For Each child As CodeElement In elements
            If child.Kind = vsCMElement.vsCMElementFunction Then
                Dim currentMethod = CType(child, CodeFunction)
                If IsNothing(currentMethod) Then
                    Exit Sub
                End If

                Dim interfaceMethod = GetInterfaceMethod(currentMethod)
                If IsNothing(interfaceMethod) Then
                    Exit Sub
                End If

                Dim newComment = GetSummaryFromDocComment(interfaceMethod.DocComment)
                AddCommentToMethod(currentMethod, newComment)
            End If
            TraverseAndCommentImplementedMethods(child.Children)
        Next
    End Sub

    Public Sub CommentAllImplementedMethodsInFile()
        Try
            TraverseAndCommentImplementedMethods(DTE.ActiveDocument.ProjectItem.FileCodeModel.CodeElements)
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
    End Sub
End Module