|
|
|
date: Wed, 4 Jun 2008 06:46:05 -0700,
group: microsoft.public.vstudio.extensibility
back
Issue with regular expression in VS2008 Macro
I've been working on a macro for several weeks now (VS2008 on Vista
Ultimate) that is designed to open every file in a Visual Studio solution
and inserts custom "trace" commands into every function and every "catch"
block. Currently, I use a regular expression that searches the current
CodeElement (the function) for "catch" blocks. The regex breaks the block up
into 5 groups...the "catch" statement, the exception type, the exception
variable, the open curly brace, and the contents inside the block.
The problem I am having, I feel, comes from how I am calculating where in
the block to insert the trace command. I use the AbsoluteCharOffset of the
current function and add that to the index of the '{' group for that specific
catch block. The problem with doing this is that my regular expression does
not seem to take newlines into account and adds the number of newlines from
the start of the function to the catch block onto the index of insertion.
First, here is some example code before the macro is run on it:
private void func1(int num)
{
try
{
try
{
func2(num);
throw new Exception("test1");
throw new NullReferenceException("test2");
throw new NotImplementedException("test3");
}
catch (NullReferenceException r)
{
MessageBox.Show(r.Message);
}
catch (NotImplementedException e)
{
MessageBox.Show(e.Message);
}
catch (Exception t)
{
MessageBox.Show(t.Message);
}
}
catch (Exception err)
{
MessageBox.Show(err.Message);
}
}
The more catch blocks present, the more offset the commands to insert
become. In the following, you can see as the commands are inserted, they
become more and more offset (normally I use a SmartFormat function after all
the insertions are completed, however I turned that off to show you how
things are being inserted.) The "function level" trace commands (inside the
try/finally block) are inserted without problem, but the catch level commands
become increasingly offset.
private void func1(int num)
{
try
{
ZynTraceLog.Trace("Threadz.Form1.func1", messageToShow.enter);
try
{
try
{
func2(num);
throw new Exception("one");
throw new NullReferenceException("two");
throw new NotImplementedException("three");
}
catch (NullReferenceException r)
{
ZynTraceLog.TraceException("Threadz.Form1.func1", r,
messageToShow.exception);
MessageBox.Show(r.Message);
}
catch (NotImplementedException e)
{
ZynTraceLog.TraceException("Threadz.Form1.func1", e,
messageToShow.exception);
MessageBox.Show(e.Message);
}
catch (Exception t)
{
ZynTraceLog.TraceException("Threadz.Form1.func1", t,
messageToShow.exception);
MessageBox.Show(t.Message);
}
}
catch (Exception err)
{
MessageBZynTraceLog.TraceException("Threadz.Form1.func1",
err, messageToShow.exception);
ox.Show(err.Message);
}
}
finally
{
ZynTraceLog.Trace("Threadz.Form1.func1", messageToShow.exit);
}
}
The following piece of code is the sub-routine that does the checks for
"catch" blocks (you will also see the regular expression used):
'This sub-routine contains the logic for checking to see if a specific '
'CodeElement contains an exception block...specifically a 'catch' block. It
'starts by using a regular expression to search the CodeElement and return
'matches. After the list of matches has been populated,
'the next step is to loop through the list and insert the "enter" trace
'command after the opening curly brace. The "exit" command is inserted at
'the end of the catch block.
Sub CheckForException(ByVal codeElem As CodeElement)
Dim beginFunction As TextPoint = codeElem.StartPoint
Dim startWrite As EditPoint2 = codeElem.StartPoint.CreateEditPoint()
Dim ending As EditPoint = codeElem.EndPoint.CreateEditPoint()
Dim expression As String = startWrite.CreateEditPoint().GetText(ending)
Dim traceStmt1 As String = ""
Dim matchList As List(Of Match) = New List(Of Match)
'The following object is of type Regex. It will be given a specific regular
'expression to search for in the text. It will attempt to find any instance
'of the following code segment: Catch ( Exception e ) { <code> }
'The regular expression class allows you to split the match up into a
"group" 'collection that can be indexed to find a specific group. An
explanation of 'the regular expression is as follows:
\w*(?<Catch>catch)\s*\(\s* ---> "<0 or more words>Catch<0 or more
whitespaces>("
(?<Exception>\w*Exception)\s* ---> "'<0 or more word(s)>Exception'<0 or
more whitespaces" ... i.e. "\w*Exception" will match NullReferenceException
(?<VarName>\w*\s*[^\)]) ---> <0 or more words> followed by <0 or more
whitespaces> UP TO (not including) the first occurence of )
[\)]\w*\s*[\r\n]\w*\s* ---> Now INCLUDE the ) in the match, followed by
<0 or more words><0 or more whitespaces><Carriage return or newline><0 or
more words><0 or more whitespaces>
(?<OpenCurly>[\{]) ---> Will match the first open curly brace found
(?<CurlyContents>.*?[\}]) ---> Matches everything inside of the "catch"
block...after the { and before the }
Dim regexAddr As Regex = New
Regex("\w*(?<Catch>catch)\s*\(\s*(?<Exception>\w*Exception)\s*(?<VarName>\w*\s*[^\)])[\)]\w*\s*[\r\n]\w*\s*(?<OpenCurly>[\{])(?<CurlyContents>.*?[\}])", _
RegexOptions.Multiline Or RegexOptions.CultureInvariant Or
RegexOptions.IgnoreCase Or RegexOptions.Singleline)
' Create a match
Dim aMatch As Match = regexAddr.Match(expression)
' Check to see if there is a match in the current function, if so, continue
If aMatch.Success Then
' Call the FillMatchArray to fill in the Match list
' Each item will have groups when each match is broken down into parts
FillMatchArray(matchList, aMatch)
' Used as an index for the list...must work backwards as to not corrupt
' the already set AbsoluteCharOffets of the open curly braces ({) for
' the catch blocks
Dim index As Integer = matchList.Count - 1
While index > -1
' Group(4) is the "curly brace" group for each match. The value is "{".
If matchList.Item(index).Groups(4).Success Then
' Move the edit point to the beginning of the function the catch is
' in PLUS the location of the "{" for each catch block
startWrite.MoveToAbsoluteOffset(beginFunction.AbsoluteCharOffset + _
(matchList.Item(index).Groups(4).Index))
traceStmt1 = "ZynTraceLog.TraceException(" & Chr(34) & _
CodeElem.FullName & Chr(34) & ", " &
matchList.Item(index).Groups(3).Value & ", messageToShow.exception);" &
vbCrLf
' Check to see if the catch trace cmd is already written
Dim blockContents = matchList.Item(index).Groups(5).ToString()
If Not blockContents.Contains(traceStmt1) Then
startWrite.Insert(traceStmt1)
End If
End If
' Decrement the index to move backwards through the list
index = index - 1
End While
End If
End Sub
date: Wed, 4 Jun 2008 06:46:05 -0700
author: Andy am
RE: Issue with regular expression in VS2008 Macro
Hi Andy,
Welcome to MSDN Managed Newsgroup!
So far my understanding of your question is:
* you are using regular expressions to parse C# code and insert some
statements at matching positions
* your current code will insert code into incorrect positions
Please feel free to let me know if I've misunderstood anything. I'll be
glad to
revise my proposed solution below.
The key issue here is that the text returned from EditPoint.GetText() is
not having the same length as the one that computed from
TextPoint.AbsoluteOffset: a linefeed takes one char when computing the
AbsoluteOffset, but the returned text will use CR LF (2 chars), thus when
you use the Match.Index and the offset together, the position is incorrect.
The solution is to replace vbCrLf with vbLf after you get the text with
EditPoint.GetText():
Dim expression As String = startWrite.CreateEditPoint().GetText(ending)
expression = expression.Replace(vbCrLf, vbLf)
Debug.Assert(expression.Length = ending.AbsoluteCharOffset -
beginFunction.AbsoluteCharOffset)
Also, when you insert statements, you need to add 1 since the offset is
1-based and Match.Index is 0-based:
startWrite.MoveToAbsoluteOffset(beginFunction.AbsoluteCharOffset + _
(matchList.Item(index).Groups(4).Index + 1))
Hope this helps. Please let me know if you have further questions.
Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
msdnmg@microsoft.com.
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.
Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
date: Thu, 05 Jun 2008 09:42:19 GMT
author: (Walter Wang [MSFT])
RE: Issue with regular expression in VS2008 Macro
Hi Andy,
Thanks for your quick update.
First, we need to make two changes for the above code:
Function CheckForException(ByVal codeElem As CodeElement)
Dim changed As Boolean = False
...
If Not blockContents.Contains(traceStmt1.Replace(vbCrLf, vbLf)) Then
startWrite.Insert(traceStmt1)
changed = True
End If
...
Return changed
End Function
1) We need to replace the vbCrLf in traceStmt1 too when determine if we had
already inserted the statements, otherwise we will insert duplicated
statements.
2) Change the procedure to function and return True if we really did some
changes, this will be used later.
As for your new question, I think it's probably because you're not checking
for correct CodeElement.Kind, in this case, my understanding is that we
only need to insert these statements in CodeElement with Kind =
vsCMElementFunction or vsCMElementProperty.
Also, the CodeElements in a FileCodeModel is tree-like. We can use code
like following to get all functions/properties kind CodeElements:
Sub GetInterestedCodeElements(ByVal result As List(Of CodeElement),
ByVal elements As CodeElements)
For Each ce As CodeElement In elements
Dim members As CodeElements = Nothing
Select Case ce.Kind
Case vsCMElement.vsCMElementNamespace
members = CType(ce, CodeNamespace).Members
Case vsCMElement.vsCMElementClass
members = CType(ce, CodeClass).Members
Case vsCMElement.vsCMElementFunction,
vsCMElement.vsCMElementProperty
result.Add(ce)
End Select
If Not members Is Nothing Then
GetInterestedCodeElements(result, members)
End If
Next
End Sub
As we can see, we need to recursively walk into Namespace and Class to get
the functions/properties. This should also work for nested classes.
With this, we can write following macros to insert statements in various
contexts:
Sub CheckExceptionForCurrentFunction()
Dim ts As TextSelection = DTE.ActiveDocument.Selection
Dim interestedKinds() As vsCMElement =
{vsCMElement.vsCMElementFunction, vsCMElement.vsCMElementProperty}
For Each kind In interestedKinds
Dim ce As CodeElement = ts.ActivePoint.CodeElement(kind)
If Not ce Is Nothing Then
CheckForException(ce)
Exit For
End If
Next
End Sub
Sub CheckExceptionForCurrentFile()
CheckExceptionForProjectItem(DTE.ActiveDocument.ProjectItem)
End Sub
Sub CheckExceptionForSelectedProjects()
For Each proj As Project In DTE.ActiveSolutionProjects
For Each pi As ProjectItem In proj.ProjectItems
CheckExceptionForProjectItem(pi)
Next
Next
End Sub
Sub CheckExceptionForAllProjects()
For Each proj As Project In DTE.Solution.Projects
For Each pi As ProjectItem In proj.ProjectItems
CheckExceptionForProjectItem(pi)
Next
Next
End Sub
NOTE: you probably need to check for correct project type (C#) before
processing all project items in a project. Check Project.Kind, which is a
GUID.
You can put these macros in context menu or toolbar for easier access.
Following is the helper function CheckExceptionForProjectItem used:
Function CheckExceptionForProjectItem(ByVal pi As ProjectItem)
Dim changed As Boolean = False
If Not pi.FileCodeModel Is Nothing Then
Dim result As New List(Of CodeElement)
GetInterestedCodeElements(result, pi.FileCodeModel.CodeElements)
For Each ce As CodeElement In result
If CheckForException(ce) Then
changed = True
End If
Next
If changed Then
pi.Open(Constants.vsViewKindCode).Visible = True
End If
End If
Return changed
End Function
Hope this helps. Again, please feel free to let me know if there's anything
unclear.
Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
msdnmg@microsoft.com.
This posting is provided "AS IS" with no warranties, and confers no rights.
date: Thu, 05 Jun 2008 23:30:39 GMT
author: (Walter Wang [MSFT])
RE: Issue with regular expression in VS2008 Macro
Thanks again for the information. The thing I'm unsure of though is how
exactly I am to integrate the examples you gave me into my current macro. I
feel like I am kind of soing the same thing you are, only a little
differently. I am going to post the whole macro I guess, just so you can see
if I am going about it the correct way. I'm sure there are more efficient
ways to do what I'm looking to do, but functionality comes first right now at
the sake of efficiency, as it is not too important for this macro to be
efficient (just has to get the job done). Thanks again.
Ryan...
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Imports System.Text.RegularExpressions
Imports System.Collections.Generic
Public Module ScanSolution
' First, this sub-routine checks to see that there is even a solution
open to begin
' working with. When there is a solution open, this will begin to
examine the projects in the solution.
' For each project, we want to examine the project items.
Sub NavigateSolution()
'Declare a project object
Dim objProject As EnvDTE.Project
Try
'First, check to see that a solution is open in the VS environment
If Not DTE.Solution.IsOpen Then
MsgBox("Please load or create a solution")
Else
'Traverse through each project item of the project inside
the solution
For Each objProject In DTE.Solution.Projects
NavigateProjectItems(objProject.ProjectItems)
Next
End If
Catch objException As System.Exception
MsgBox(objException.ToString)
End Try
End Sub
' Called from the NavigateSolution routine, this sub-routine is
responsible for checking the contents
' of a specific project item...whether a namespace, class, variable, etc.
Private Sub NavigateProjectItems(ByVal projItems As ProjectItems)
'Declare a project item
Dim projItem As EnvDTE.ProjectItem
' Check to see if there is any "project items" (code files, resource
files, etc.)
' in the project...if not, return to function call
If Not (projItems Is Nothing) Then
For Each projItem In projItems
GoThroughProjectItem(projItem)
Next
End If
End Sub
' Once called, this routine must process the ProjectItem passed to it.
Because this could be any number of
' "project items", we must first check what kind of project item it is.
We will only be processing files ending
' in .cs (C# files). If the project item has a file code model (i.e. it
has a code structure like a class file), then
' we must traverse through it finding code elements (functions and other
code blocks)
Sub GoThroughProjectItem(ByVal projItem As ProjectItem)
'Declare a code element...Could be a statement, namespace, class,
function, etc.
Dim elem As CodeElement
If Not IsNothing(projItem) Then
'Is it a file?
If projItem.Kind = "{6BB5F8EE-4483-11D3-8BCF-00C04F8EC28C}" And
projItem.Name.EndsWith(".cs") And Not
(projItem.Name.EndsWith(".Designer.cs")) Then
projItem.Open(Constants.vsViewKindCode) 'it's a file, open it
projItem.Document.Activate() 'Now make it active (this is
for the smart formatting function
End If
If Not projItem.Name.EndsWith(".Designer.cs") Then
If Not IsNothing(projItem.FileCodeModel) Then
GoThroughCodeElements(elem, projItem)
ElseIf Not IsNothing(projItem.ProjectItems) Then
NavigateProjectItems(projItem.ProjectItems)
End If
End If
End If
End Sub
' Called when a project item has a FileCodeModel property.
' This will walk through a code model until it finds the "class" code
element.
' Once that is found, it is passed to the GoThroughClass sub-routine to
process the elements in the class
Sub GoThroughCodeElements(ByVal elem As CodeElement, ByVal projItem As
ProjectItem)
'Drill down through code elements
For Each elem In projItem.FileCodeModel.CodeElements
If elem.Kind = vsCMElement.vsCMElementNamespace Then
GoThroughClass(elem)
End If
Next
' Now call SmartFormat() which will set all of the tabs and indents
for the code
SmartFormat()
End Sub
' This routine handles all of the logic for inserting the trace commands
or calling another routine
' to insert instead. The function will call itself until it finds
either a get/set block or a function.
' When a function is found, it is first scanned for any exceptions
inside. After that, the edit points are
' created (where the trace commands are inserted in the code). Once
they are set, the actual commands are
' set up as strings using things like the code element name and the
enter or exit option.
Sub GoThroughClass(ByVal elem As CodeElement)
'This variable will be children of elem
Dim childCodeElem As CodeElement
Dim enterString1 As String
Dim exitString1 As String
Dim newLine As String = vbCrLf
Dim entryEdit As EditPoint
Dim exitEdit As EditPoint
Dim getOrSet As CodeProperty
For Each childCodeElem In elem.Children
If childCodeElem.Kind = vsCMElement.vsCMElementClass Then
GoThroughClass(childCodeElem)
ElseIf childCodeElem.Kind = vsCMElement.vsCMElementFunction Then
CheckForException(childCodeElem)
Dim codeElemFunction = CType(childCodeElem, CodeFunction)
If Not codeElemFunction.MustImplement Then
'Set the point in the code at which you plan to edit
Try
entryEdit =
codeElemFunction.GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint()
Catch ex As Exception
End Try
Try
exitEdit =
codeElemFunction.GetEndPoint(vsCMPart.vsCMPartBody).CreateEditPoint()
Catch ex As Exception
End Try
If Not IsNothing(entryEdit) And Not IsNothing(exitEdit)
Then
'Set the strings to insert into the code
'Chr(135) is delimeter for string
enterString1 = "try" & newLine & "{" & newLine &
"ZynTraceLog.Trace(" & Chr(34) & codeElemFunction.FullName & Chr(34) & ",
messageToShow.enter"
exitString1 = "}" & newLine & "finally" & newLine &
"{" & newLine & "ZynTraceLog.Trace(" & Chr(34) & codeElemFunction.FullName &
Chr(34) & ", messageToShow.exit"
enterString1 += ");"
exitString1 += ");" & newLine & "}"
'Check to see if the strings already exist in the
code, if so, do not write anything
'We do the exitString first in the case that there
is no text written within the function.
If IsWrittenInFunction(childCodeElem, exitString1) =
False Then
exitEdit.Insert(exitString1 & newLine)
End If
If IsWrittenInFunction(childCodeElem, enterString1)
= False Then
entryEdit.Insert(enterString1 & newLine)
End If
End If
End If
ElseIf childCodeElem.Kind = vsCMElement.vsCMElementProperty Then
' Set getOrSet to a CodeProperty object
getOrSet = CType(childCodeElem, CodeProperty)
' Call ManipGetSet() to add the trace code to the get/set
properties that may are present
ManipGetSet(getOrSet)
End If
Next
End Sub
' This will make a collection of arguments that is the list of arguments
for a specific function.
' This list must also be appended to the trace command that inserted
into the code.
Sub setStringWithParams(ByRef enterStringCopy As String, ByRef
exitStringCopy As String, ByVal codeElem As CodeFunction)
Dim index As Integer = 1
Dim param As CodeParameter
Dim argDict As Dictionary(Of String, Object) = New Dictionary(Of
String, Object)
For Each param In codeElem.Parameters
argDict.Add(codeElem.Parameters.Item(index).FullName,
codeElem.Parameters.Item(index))
index = index + 1
Next
enterStringCopy += ", argdict"
exitStringCopy += ", argdict"
End Sub
' This sub-routine contains the logic for checking to see if a specific
CodeElement contains
' an exception block...specifically a 'catch' block. It starts by using
a regular expression
' to search the CodeElement and return matches. After the list of
matches has been populated,
' the next step is to loop through the list and insert the "enter" trace
command after the opening
' curly brace. The "exit" command is inserted at the end of the catch
block.
Function CheckForException(ByVal codeElem As CodeElement)
Dim beginFunction As TextPoint = codeElem.StartPoint
Dim startWrite As EditPoint2 = codeElem.StartPoint.CreateEditPoint()
Dim ending As EditPoint = codeElem.EndPoint.CreateEditPoint()
Dim expression As String =
startWrite.CreateEditPoint().GetText(ending)
expression = expression.Replace(vbCrLf, vbLf)
Dim traceStmt1 As String = ""
Dim matchList As List(Of Match) = New List(Of Match)
Dim changed As Boolean = False
' The following object is of type Regex. It will be given a
specific regular expression to search for in the text.
' It will attempt to find any instance of the following code
segment: Catch ( Exception e ) { <code> }
' The regular expression class allows you to split the match up into
a "group" collection that can be indexed to find a specific group
' An explanation of the regular expression is as follows:
' \w*(?<Catch>catch)\s*\(\s* ---> "<0 or more
words>Catch<0 or more whitespaces>("
' (?<Exception>\w*Exception)\s* ---> "'<0 or more
word(s)>Exception'<0 or more whitespaces" ... i.e. "\w*Exception" will match
NullReferenceException
' (?<VarName>\w*\s*[^\)]) ---> <0 or more words> followed
by <0 or more whitespaces> UP TO (not including) the first occurence of )
' [\)]\w*\s*[\r\n]\w*\s* ---> Now INCLUDE the ) in the
match, followed by <0 or more words><0 or more whitespaces><Carriage return
or newline><0 or more words><0 or more whitespaces>
' (?<OpenCurly>[\{]) ---> Will match the first open curly
brace found
' (?<CurlyContents>.*?[\}]) ---> Matches everything
inside of the "catch" block...after the { and before the }
Dim regexAddr As Regex = New
Regex("\w*(?<Catch>catch)\s*\(\s*(?<Exception>\w*Exception)\s*(?<VarName>\w*\s*[^\)])[\)]\w*\s*[\r\n]\w*\s*(?<OpenCurly>[\{])(?<CurlyContents>.*?[\}])", _
RegexOptions.Multiline Or
RegexOptions.CultureInvariant Or RegexOptions.IgnoreCase Or
RegexOptions.Singleline)
' Create a match
Dim aMatch As Match = regexAddr.Match(expression)
' Check to see if there is a match in the current function, if so,
continue
If aMatch.Success Then
' Call the FillMatchArray to fill in the Match list
' Each item will have groups when each match is broken down into
parts
FillMatchArray(matchList, aMatch)
' Used as an index for the list...must work backwards as to not
corrupt the already set AbsoluteCharOffets of the open curly braces ({) for
the catch blocks
Dim index As Integer = matchList.Count - 1
While index > -1
' Group(4) is the "curly brace" group for each match. The
value is "{".
If matchList.Item(index).Groups(4).Success Then
' Move the edit point to the beginning of the function
the catch is in PLUS the location of the "{" for each catch block
startWrite.MoveToAbsoluteOffset(beginFunction.AbsoluteCharOffset +
(matchList.Item(index).Groups(4).Index) + 1)
'startWrite.StartOfLine()
traceStmt1 = "ZynTraceLog.TraceException(" & Chr(34) &
codeElem.FullName & Chr(34) & ", " & matchList.Item(index).Groups(3).Value &
", messageToShow.exception);" & vbCrLf
' Check to see if the catch trace cmd is already written
Dim blockContents =
matchList.Item(index).Groups(5).ToString()
If Not blockContents.Contains(traceStmt1.Replace(vbCrLf,
vbLf)) Then
startWrite.Insert(traceStmt1)
changed = True
End If
End If
' Decrement the index to move backwards through the list
index = index - 1
End While
End If
Return changed
End Function
' This sub-routine will add all of the successful matches to a List of
type Match
' Each Match in the list will be broken down into groups
Sub FillMatchArray(ByRef matchList As List(Of Match), ByVal theMatch As
Match)
Do While Not (theMatch.Value = "")
matchList.Add(theMatch)
theMatch = theMatch.NextMatch
Loop
End Sub
' This sub-routine sets up the arguments for the WritePropertyTrace
routine to insert the necessary
' trace commands into the code file. Not every property block has BOTH
a 'get' and 'set' block, and
' this is the routine that checks for that.
Sub ManipGetSet(ByVal prop As CodeProperty)
' Get the "getter" property block
Dim getter As CodeFunction = prop.Getter
' Get the "setter" property block
Dim setter As CodeFunction = prop.Setter
If Not IsNothing(getter) Then
WritePropertyTrace(getter, "get")
End If
If Not IsNothing(setter) Then
WritePropertyTrace(setter, "set")
End If
End Sub
' This sub-routine
Sub WritePropertyTrace(ByVal accessor As CodeFunction, ByVal type As
String)
Dim writeEnter As String = "ZynTraceLog.Trace(" & Chr(34) &
accessor.FullName & Chr(34) & ", messageToShow.enter_" & type & ");"
Dim writeExit As String = "ZynTraceLog.Trace(" & Chr(34) &
accessor.FullName & Chr(34) & ", messageToShow.exit_" & type & ");"
' Set the editpoints for each property block
Dim enterEdit As EditPoint =
accessor.GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint()
Dim exitEdit As EditPoint =
accessor.GetEndPoint(vsCMPart.vsCMPartBody).CreateEditPoint()
If type.Equals("get") Then
writeEnter = "try" & vbCrLf & "{" & vbCrLf & writeEnter
writeExit = "}" & vbCrLf & "finally" & vbCrLf & "{" & vbCrLf &
writeExit & vbCrLf & "}"
End If
' Check to see if the string to instert is already written within
the property block
If IsAlreadyWrittenInGetSet(accessor, writeEnter) = False Then
enterEdit.Insert(writeEnter & vbCrLf)
End If
If IsAlreadyWrittenInGetSet(accessor, writeExit) = False Then
exitEdit.Insert(writeExit & vbCrLf)
End If
End Sub
' This function has a CodeFunction and String as arguments and returns a
bool.
' It examines the start and end points of a get or set block and copies
the contents of that into a string.
' It then checks to see if that new string contains the other string
that is passed in as an argument
' If it contains the string, true is returned, else false
Function IsAlreadyWrittenInGetSet(ByVal getterOrSetter As CodeFunction,
ByVal str As String) As Boolean
' Set the start point for the text selection
Dim start As TextPoint = getterOrSetter.StartPoint()
' Set the end point for the text selection
Dim finish As TextPoint = getterOrSetter.EndPoint()
' Select the text from those pre-set points and push it into a
string to parse later
Dim selection As String = start.CreateEditPoint().GetText(finish)
Dim removedWS = selection.Replace(" ", "")
Dim strNonWS = str.Replace(" ", "")
' Returns true if the text selection contains str
Return removedWS.Contains(strNonWS)
End Function
' This function does the same thing as IsAlreadyWrittenInGetSet except
it checks the whole function rather
' than just a piece like a get block.
Function IsWrittenInFunction(ByVal elem As CodeElement, ByVal str As
String) As Boolean
' Set the start point for the text selection
Dim startPoint As TextPoint = elem.StartPoint()
' Set the end point for the text selection
Dim endPoint As TextPoint = elem.EndPoint()
' Select the text from those pre-set points and push it into a
string to parse later
Dim functionAsString As String =
startPoint.CreateEditPoint.GetText(endPoint)
' Remove the white space from the strings
Dim removedWS = functionAsString.Replace(" ", "")
Dim strNonWS = str.Replace(" ", "")
' Returns true if the text selection contains the string
Return removedWS.Contains(strNonWS)
End Function
' This sub-routine is responsible for formatting the active document.
' It will move all the indents and other spacing into the corrent format
Sub SmartFormat()
' Before running this example, open a code document.
Dim objSel As TextSelection = DTE.ActiveDocument.Selection
' Select all code in open document.
objSel.SelectAll()
' Apply smart formatting rules for the language.
objSel.SmartFormat()
End Sub
End Module
date: Fri, 6 Jun 2008 07:56:02 -0700
author: Andy am
RE: Issue with regular expression in VS2008 Macro
Thanks for further information.
I think for simple projects, using regex to insert/remove trace statements
should be fine. However, this approach does have some limitations: it's
difficult to accurately find valid functions/properties, for example, what
if some commented out code that also have functions/properties? That's
where a language lexer/parser comes into play. And building a lexer/parser
is not trivial.
Also, to log when entering/leaving a function, this is what AOP
(Aspect-Oriented Programming) does best. You can find more information
about AOP in .NET here:
http://blogs.msdn.com/abhinaba/archive/2006/01/23/516163.aspx.
One of the approaches that AOP takes to add log when entering/leaving
function is to create a dynamic proxy that wraps the actual function call.
Unfortunately this also means it cannot help you to insert statements in
every try/catch block which is inside the function body.
As a side note, I'm curious to know why you need to add trace statements in
every try/catch block. Some best practice is to only catch an exception if
you can handle. Then, at places where you do catch the exception, you can
get the exception's full stack trace which contains correct information for
logging purpose. You might find following articles useful:
http://www.codeproject.com/KB/cs/csmverrorhandling.aspx
http://www.codeproject.com/KB/architecture/exceptionbestpractices.aspx
I'm sorry that this discussion seems a bit off-topic to the original
question. Have you fixed the second question about CodeElement casting
error? Please feel free to let me know if it's still not fixed.
Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
msdnmg@microsoft.com.
This posting is provided "AS IS" with no warranties, and confers no rights.
date: Sat, 07 Jun 2008 03:14:02 GMT
author: (Walter Wang [MSFT])
RE: Issue with regular expression in VS2008 Macro
Walter,
I can send you the macro project (but it is nothing more than you have).
The only thing that I've tested it on is our product, and I cannot send you
our source code for the product unfortunatly. Hopefully there is still some
information you could provide as to why this error could be happening,
however I understand if we are at a standstill. One thing test that I would
like to try, and perhaps you could tell me how (and if it is even possible),
is if let's say I run the macro, and it fails on file 1000 for example,
rather than restarting at file 0 when I re-run the macro, is it possible to
start at file 1001, and proceed down the list of files?
date: Tue, 10 Jun 2008 05:55:02 -0700
author: Andy am
RE: Issue with regular expression in VS2008 Macro
Hi,
I was able to reproduce the issue as your described on a large solution
with 400+ files, though I'm not sure if it's the same exception you're
experiencing. I have done some research and testing, and I think I may have
found some workaround.
First, a CodeFunction may not have implementation at all, therefore calling
its GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint() will fail. Two
examples of such CodeFunctions are:
* functions with [DllImport] attribute
* auto property in C# 3.0
To fix this, we can use following function to determine if a CodeFunction
has body at all:
Function HasCSharpCodeBody(ByVal cf As CodeFunction)
Return
cf.StartPoint.CreateEditPoint().GetText(cf.EndPoint).Contains("{")
End Function
The second issue here is that we may encounter random exception when
running the macro on a large solution. I haven't been able to find the root
cause, but I found if we do the same thing from an addin, then it's working
fine. I will forward these information to developer team for further
investigation. For now, I suggest you to use following steps to migrate the
macro to an addin:
1) In Visual Studio 2008, Create a new project with type "Visual Studio
Add-in" in category "Other Project Types\Extensibility".
2) Follow the wizard and select "Visual Basic" as the programming language
in Page 1 of 6
3) Next, we only need to select "Microsoft Visual Studio 2008" as
Application Host
4) In the step "Choosing Add-in Options", check the first option to
"Create a 'Tools' menu item", we can use this menu item to invoke our
function that was called by a macro
5) After the add-in project is created, create a new module and copy/paste
your macro module into it, and declare a module variable DTE:
Public DTE As DTE2
6) In Connect.vb, modify the Exec procedure as:
Public Sub Exec(ByVal commandName As String, ByVal executeOption As
vsCommandExecOption, ByRef varIn As Object, ByRef varOut As Object, ByRef
handled As Boolean) Implements IDTCommandTarget.Exec
handled = False
If executeOption = vsCommandExecOption.vsCommandExecOptionDoDefault
Then
If commandName = "MyAddin1.Connect.MyAddin1" Then
ScanSolution.DTE = _applicationObject
ScanSolution.NavigateSolution()
handled = True
Exit Sub
End If
End If
End Sub
7) Your macro module should compile without further modification. Now
build the addin and start a new instance of Visual Studio, you should be
able to see a new menu item in the Tools menu to launch your addin.
NOTE: do not try to debug the addin under Visual Studio debugger, it will
have the same issue as we are experiencing from the macro.
I hope this workaround will also work for you. Please let me know if you
have any questions.
Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
msdnmg@microsoft.com.
This posting is provided "AS IS" with no warranties, and confers no rights.
date: Wed, 11 Jun 2008 14:24:52 GMT
author: (Walter Wang [MSFT])
|
|