/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/VisualBasic/Portable/Lowering/Instrumentation/CodeCoverageInstrumenter.vb
556 строк
31 KB
Tomáš Matoušek
Instrumentation state (#66429)
30 янв 2023, 22:41
Не верифицирован
30 янв 2023, 22:41
c29e198
Код
Авторство
О чём код?
' Licensed to the .NET Foundation under one or more agreements. ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable Imports Microsoft.Cci Imports Microsoft.CodeAnalysis.CodeGen Imports Microsoft.CodeAnalysis.Collections Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic ''' <summary> ''' This type provides means for instrumenting compiled methods for dynamic analysis. ''' It can be combined with other <see cref= "Instrumenter"/>s. ''' </summary> Friend NotInheritable Class CodeCoverageInstrumenter Inherits CompoundInstrumenter Private ReadOnly _method As MethodSymbol Private ReadOnly _methodBody As BoundStatement Private ReadOnly _createPayloadForMethodsSpanningSingleFile As MethodSymbol Private ReadOnly _createPayloadForMethodsSpanningMultipleFiles As MethodSymbol Private ReadOnly _spansBuilder As ArrayBuilder(Of SourceSpan) Private _dynamicAnalysisSpans As ImmutableArray(Of SourceSpan) = ImmutableArray(Of SourceSpan).Empty Private ReadOnly _methodEntryInstrumentation As BoundStatement Private ReadOnly _payloadType As ArrayTypeSymbol Private ReadOnly _methodPayload As LocalSymbol Private ReadOnly _diagnostics As BindingDiagnosticBag Private ReadOnly _debugDocumentProvider As DebugDocumentProvider Private ReadOnly _methodBodyFactory As SyntheticBoundNodeFactory Public Shared Function TryCreate( method As MethodSymbol, methodBody As BoundStatement, methodBodyFactory As SyntheticBoundNodeFactory, diagnostics As BindingDiagnosticBag, debugDocumentProvider As DebugDocumentProvider, previous As Instrumenter) As CodeCoverageInstrumenter ' Do not instrument implicitly-declared methods, except for constructors. ' Instrument implicit constructors in order to instrument member initializers. If method.IsImplicitlyDeclared AndAlso Not method.IsAnyConstructor Then Return Nothing End If ' Do not instrument if the syntax node does not have a valid mapped line span. If Not HasValidMappedLineSpan(methodBody.Syntax) Then Return Nothing End If ' Do not instrument methods marked with or in scope of ExcludeFromCodeCoverageAttribute If IsExcludedFromCodeCoverage(method) Then Return Nothing End If Dim createPayloadForMethodsSpanningSingleFile As MethodSymbol = GetCreatePayloadOverload( methodBodyFactory.Compilation, WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, methodBody.Syntax, diagnostics) Dim createPayloadForMethodsSpanningMultipleFiles As MethodSymbol = GetCreatePayloadOverload( methodBodyFactory.Compilation, WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles, methodBody.Syntax, diagnostics) ' Do not instrument any methods if CreatePayload is not present. If createPayloadForMethodsSpanningSingleFile Is Nothing OrElse createPayloadForMethodsSpanningMultipleFiles Is Nothing Then Return Nothing End If ' Do not instrument CreatePayload if it is part of the current compilation (which occurs only during testing). ' CreatePayload will fail at run time with an infinite recursion if it is instrumented. If method.Equals(createPayloadForMethodsSpanningSingleFile) OrElse method.Equals(createPayloadForMethodsSpanningMultipleFiles) Then Return Nothing End If Return New CodeCoverageInstrumenter( method, methodBody, methodBodyFactory, createPayloadForMethodsSpanningSingleFile, createPayloadForMethodsSpanningMultipleFiles, diagnostics, debugDocumentProvider, previous) End Function Private Shared Function HasValidMappedLineSpan(syntax As SyntaxNode) As Boolean Return syntax.GetLocation().GetMappedLineSpan().IsValid End Function Public ReadOnly Property DynamicAnalysisSpans As ImmutableArray(Of SourceSpan) Get Return _dynamicAnalysisSpans End Get End Property Private Sub New( method As MethodSymbol, methodBody As BoundStatement, methodBodyFactory As SyntheticBoundNodeFactory, createPayloadForMethodsSpanningSingleFile As MethodSymbol, createPayloadForMethodsSpanningMultipleFiles As MethodSymbol, diagnostics As BindingDiagnosticBag, debugDocumentProvider As DebugDocumentProvider, previous As Instrumenter) MyBase.New(previous) _createPayloadForMethodsSpanningSingleFile = createPayloadForMethodsSpanningSingleFile _createPayloadForMethodsSpanningMultipleFiles = createPayloadForMethodsSpanningMultipleFiles _method = method _methodBody = methodBody _spansBuilder = ArrayBuilder(Of SourceSpan).GetInstance() Dim payloadElementType As TypeSymbol = methodBodyFactory.SpecialType(SpecialType.System_Boolean) _payloadType = ArrayTypeSymbol.CreateVBArray(payloadElementType, ImmutableArray(Of CustomModifier).Empty, 1, methodBodyFactory.Compilation.Assembly) _methodPayload = methodBodyFactory.SynthesizedLocal(_payloadType, kind:=SynthesizedLocalKind.InstrumentationPayload, syntax:=methodBody.Syntax) _diagnostics = diagnostics _debugDocumentProvider = debugDocumentProvider _methodBodyFactory = methodBodyFactory ' The first point indicates entry into the method and has the span of the method definition. Dim bodySyntax As SyntaxNode = methodBody.Syntax If Not method.IsImplicitlyDeclared Then _methodEntryInstrumentation = AddAnalysisPoint(bodySyntax, SkipAttributes(bodySyntax), methodBodyFactory) End If End Sub Private Shared Function IsExcludedFromCodeCoverage(method As MethodSymbol) As Boolean Dim containingType = method.ContainingType While containingType IsNot Nothing If containingType.IsDirectlyExcludedFromCodeCoverage Then Return True End If containingType = containingType.ContainingType End While ' Skip lambdas. They can't have custom attributes. Dim nonLambda = method.ContainingNonLambdaMember() If nonLambda?.Kind = SymbolKind.Method Then method = DirectCast(nonLambda, MethodSymbol) If method.IsDirectlyExcludedFromCodeCoverage Then Return True End If Dim associatedSymbol = method.AssociatedSymbol Select Case associatedSymbol?.Kind Case SymbolKind.Property If DirectCast(associatedSymbol, PropertySymbol).IsDirectlyExcludedFromCodeCoverage Then Return True End If Case SymbolKind.Event If DirectCast(associatedSymbol, EventSymbol).IsDirectlyExcludedFromCodeCoverage Then Return True End If End Select End If Return False End Function Private Shared Function GetCreatePayloadStatement( dynamicAnalysisSpans As ImmutableArray(Of SourceSpan), methodBodySyntax As SyntaxNode, methodPayload As LocalSymbol, createPayloadForMethodsSpanningSingleFile As MethodSymbol, createPayloadForMethodsSpanningMultipleFiles As MethodSymbol, mvid As BoundExpression, methodToken As BoundExpression, payloadSlot As BoundExpression, methodBodyFactory As SyntheticBoundNodeFactory, debugDocumentProvider As DebugDocumentProvider) As BoundExpressionStatement Dim createPayloadOverload As MethodSymbol Dim fileIndexOrIndicesArgument As BoundExpression If dynamicAnalysisSpans.IsEmpty Then createPayloadOverload = createPayloadForMethodsSpanningSingleFile ' For a compiler generated method that has no 'real' spans, we emit the index for ' the document corresponding to the syntax node that is associated with its bound node. Dim document = GetSourceDocument(debugDocumentProvider, methodBodySyntax) fileIndexOrIndicesArgument = methodBodyFactory.SourceDocumentIndex(document) Else Dim documents = PooledHashSet(Of DebugSourceDocument).GetInstance() Dim fileIndices = ArrayBuilder(Of BoundExpression).GetInstance() For Each span In dynamicAnalysisSpans Dim document = span.Document If documents.Add(document) Then fileIndices.Add(methodBodyFactory.SourceDocumentIndex(document)) End If Next documents.Free() ' At this point, we should have at least one document since we have already ' handled the case where method has no 'real' spans (and therefore no documents) above. If fileIndices.Count = 1 Then createPayloadOverload = createPayloadForMethodsSpanningSingleFile fileIndexOrIndicesArgument = fileIndices.Single() Else createPayloadOverload = createPayloadForMethodsSpanningMultipleFiles ' Order of elements in fileIndices should be deterministic because these ' elements were added based on order of spans in dynamicAnalysisSpans above. fileIndexOrIndicesArgument = methodBodyFactory.Array( methodBodyFactory.SpecialType(SpecialType.System_Int32), fileIndices.ToImmutable()) End If fileIndices.Free() End If Return methodBodyFactory.Assignment( methodBodyFactory.Local(methodPayload, isLValue:=True), methodBodyFactory.Call( Nothing, createPayloadOverload, mvid, methodToken, fileIndexOrIndicesArgument, payloadSlot, methodBodyFactory.Literal(dynamicAnalysisSpans.Length))) End Function Public Overrides Function CreateBlockPrologue(trueOriginal As BoundBlock, original As BoundBlock, ByRef synthesizedLocal As LocalSymbol) As BoundStatement Dim previousPrologue As BoundStatement = MyBase.CreateBlockPrologue(trueOriginal, original, synthesizedLocal) If _methodBody Is trueOriginal Then _dynamicAnalysisSpans = _spansBuilder.ToImmutableAndFree() ' In the future there will be multiple analysis kinds. Const analysisKind As Integer = 0 Dim modulePayloadType As ArrayTypeSymbol = ArrayTypeSymbol.CreateVBArray(_payloadType, ImmutableArray(Of CustomModifier).Empty, 1, _methodBodyFactory.Compilation.Assembly) ' Synthesize the initialization of the instrumentation payload array, using concurrency-safe code ' ' Dim payload = PID.PayloadRootField(methodIndex) ' If payload Is Nothing Then ' payload = Instrumentation.CreatePayload(mvid, methodIndex, fileIndexOrIndices, PID.PayloadRootField(methodIndex), payloadLength) ' End If Dim payloadInitialization As BoundStatement = _methodBodyFactory.Assignment( _methodBodyFactory.Local(_methodPayload, isLValue:=True), _methodBodyFactory.ArrayAccess( _methodBodyFactory.InstrumentationPayloadRoot(analysisKind, modulePayloadType, isLValue:=False), isLValue:=False, indices:=ImmutableArray.Create(_methodBodyFactory.MethodDefIndex(_method)))) Dim mvid As BoundExpression = _methodBodyFactory.ModuleVersionId(isLValue:=False) Dim methodToken As BoundExpression = _methodBodyFactory.MethodDefIndex(_method) Dim payloadSlot As BoundExpression = _methodBodyFactory.ArrayAccess( _methodBodyFactory.InstrumentationPayloadRoot(analysisKind, modulePayloadType, isLValue:=False), isLValue:=True, indices:=ImmutableArray.Create(_methodBodyFactory.MethodDefIndex(_method))) Dim createPayloadCall As BoundStatement = GetCreatePayloadStatement( _dynamicAnalysisSpans, _methodBody.Syntax, _methodPayload, _createPayloadForMethodsSpanningSingleFile, _createPayloadForMethodsSpanningMultipleFiles, mvid, methodToken, payloadSlot, _methodBodyFactory, _debugDocumentProvider) Dim payloadNullTest As BoundExpression = _methodBodyFactory.Binary( BinaryOperatorKind.Equals, _methodBodyFactory.SpecialType(SpecialType.System_Boolean), _methodBodyFactory.Local(_methodPayload, False), _methodBodyFactory.Null(_payloadType)) Dim payloadIf As BoundStatement = _methodBodyFactory.If(payloadNullTest, createPayloadCall) Debug.Assert(synthesizedLocal Is Nothing) synthesizedLocal = _methodPayload Dim prologueStatements As ArrayBuilder(Of BoundStatement) = ArrayBuilder(Of BoundStatement).GetInstance(If(previousPrologue Is Nothing, 3, 4)) prologueStatements.Add(payloadInitialization) prologueStatements.Add(payloadIf) If _methodEntryInstrumentation IsNot Nothing Then prologueStatements.Add(_methodEntryInstrumentation) End If If previousPrologue IsNot Nothing Then prologueStatements.Add(previousPrologue) End If Return _methodBodyFactory.StatementList(prologueStatements.ToImmutableAndFree()) End If Return previousPrologue End Function Public Overrides Function InstrumentExpressionStatement(original As BoundExpressionStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentExpressionStatement(original, rewritten)) End Function Public Overrides Function InstrumentStopStatement(original As BoundStopStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentStopStatement(original, rewritten)) End Function Public Overrides Function InstrumentEndStatement(original As BoundEndStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentEndStatement(original, rewritten)) End Function Public Overrides Function InstrumentContinueStatement(original As BoundContinueStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentContinueStatement(original, rewritten)) End Function Public Overrides Function InstrumentExitStatement(original As BoundExitStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentExitStatement(original, rewritten)) End Function Public Overrides Function InstrumentGotoStatement(original As BoundGotoStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentGotoStatement(original, rewritten)) End Function Public Overrides Function InstrumentRaiseEventStatement(original As BoundRaiseEventStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentRaiseEventStatement(original, rewritten)) End Function Public Overrides Function InstrumentReturnStatement(original As BoundReturnStatement, rewritten As BoundStatement) As BoundStatement Dim previous As BoundStatement = MyBase.InstrumentReturnStatement(original, rewritten) If Not original.IsEndOfMethodReturn Then If original.ExpressionOpt IsNot Nothing Then ' Synthesized return statements require instrumentation if they return values, ' e.g. in simple expression lambdas. Return CollectDynamicAnalysis(original, previous) Else Return AddDynamicAnalysis(original, previous) End If End If Return previous End Function Public Overrides Function InstrumentThrowStatement(original As BoundThrowStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentThrowStatement(original, rewritten)) End Function Public Overrides Function InstrumentOnErrorStatement(original As BoundOnErrorStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentOnErrorStatement(original, rewritten)) End Function Public Overrides Function InstrumentResumeStatement(original As BoundResumeStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentResumeStatement(original, rewritten)) End Function Public Overrides Function InstrumentAddHandlerStatement(original As BoundAddHandlerStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentAddHandlerStatement(original, rewritten)) End Function Public Overrides Function InstrumentRemoveHandlerStatement(original As BoundRemoveHandlerStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentRemoveHandlerStatement(original, rewritten)) End Function Public Overrides Function InstrumentSyncLockObjectCapture(original As BoundSyncLockStatement, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentSyncLockObjectCapture(original, rewritten)) End Function Public Overrides Function InstrumentWhileStatementConditionalGotoStart(original As BoundWhileStatement, ifConditionGotoStart As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentWhileStatementConditionalGotoStart(original, ifConditionGotoStart)) End Function Public Overrides Function InstrumentDoLoopStatementEntryOrConditionalGotoStart(original As BoundDoLoopStatement, ifConditionGotoStartOpt As BoundStatement) As BoundStatement Dim previous As BoundStatement = MyBase.InstrumentDoLoopStatementEntryOrConditionalGotoStart(original, ifConditionGotoStartOpt) If original.ConditionOpt IsNot Nothing Then Return AddDynamicAnalysis(original, previous) End If Return previous End Function Public Overrides Function InstrumentIfStatementConditionalGoto(original As BoundIfStatement, condGoto As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentIfStatementConditionalGoto(original, condGoto)) End Function Public Overrides Function CreateSelectStatementPrologue(original As BoundSelectStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.CreateSelectStatementPrologue(original)) End Function Public Overrides Function InstrumentFieldOrPropertyInitializer(original As BoundFieldOrPropertyInitializer, rewritten As BoundStatement, symbolIndex As Integer, createTemporary As Boolean) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentFieldOrPropertyInitializer(original, rewritten, symbolIndex, createTemporary)) End Function Public Overrides Function InstrumentForEachLoopInitialization(original As BoundForEachStatement, initialization As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentForEachLoopInitialization(original, initialization)) End Function Public Overrides Function InstrumentForLoopInitialization(original As BoundForToStatement, initialization As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentForLoopInitialization(original, initialization)) End Function Public Overrides Function InstrumentLocalInitialization(original As BoundLocalDeclaration, rewritten As BoundStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.InstrumentLocalInitialization(original, rewritten)) End Function Public Overrides Function CreateUsingStatementPrologue(original As BoundUsingStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.CreateUsingStatementPrologue(original)) End Function Public Overrides Function CreateWithStatementPrologue(original As BoundWithStatement) As BoundStatement Return AddDynamicAnalysis(original, MyBase.CreateWithStatementPrologue(original)) End Function Private Function AddDynamicAnalysis(original As BoundStatement, rewritten As BoundStatement) As BoundStatement If Not original.WasCompilerGenerated Then Return CollectDynamicAnalysis(original, rewritten) End If Return rewritten End Function Private Function CollectDynamicAnalysis(original As BoundStatement, rewritten As BoundStatement) As BoundStatement ' Instrument the statement using a factory with the same syntax as the statement, so that the instrumentation appears to be part of the statement. Dim statementFactory As New SyntheticBoundNodeFactory(_methodBodyFactory.TopLevelMethod, _method, original.Syntax, _methodBodyFactory.CompilationState, _diagnostics) Dim analysisPoint As BoundStatement = AddAnalysisPoint(SyntaxForSpan(original), statementFactory) Return If(rewritten IsNot Nothing, statementFactory.StatementList(analysisPoint, rewritten), analysisPoint) End Function Private Shared Function GetSourceDocument(debugDocumentProvider As DebugDocumentProvider, syntax As SyntaxNode) As Cci.DebugSourceDocument Return GetSourceDocument(debugDocumentProvider, syntax, syntax.GetLocation().GetMappedLineSpan()) End Function Private Shared Function GetSourceDocument(debugDocumentProvider As DebugDocumentProvider, syntax As SyntaxNode, span As FileLinePositionSpan) As Cci.DebugSourceDocument Dim path As String = span.Path ' If the path for the syntax node is empty, try the path for the entire syntax tree. If path.Length = 0 Then path = syntax.SyntaxTree.FilePath End If Return debugDocumentProvider.Invoke(path, basePath:="") End Function Private Function AddAnalysisPoint(syntaxForSpan As SyntaxNode, alternateSpan As Text.TextSpan, statementFactory As SyntheticBoundNodeFactory) As BoundStatement Return AddAnalysisPoint(syntaxForSpan, syntaxForSpan.SyntaxTree.GetMappedLineSpan(alternateSpan), statementFactory) End Function Private Function AddAnalysisPoint(syntaxForSpan As SyntaxNode, statementFactory As SyntheticBoundNodeFactory) As BoundStatement Return AddAnalysisPoint(syntaxForSpan, syntaxForSpan.GetLocation().GetMappedLineSpan(), statementFactory) End Function Private Function AddAnalysisPoint(syntaxForSpan As SyntaxNode, span As FileLinePositionSpan, statementFactory As SyntheticBoundNodeFactory) As BoundStatement ' Add an entry in the spans array. Dim spansIndex As Integer = _spansBuilder.Count _spansBuilder.Add(New SourceSpan( GetSourceDocument(_debugDocumentProvider, syntaxForSpan, span), span.StartLinePosition.Line, span.StartLinePosition.Character, span.EndLinePosition.Line, span.EndLinePosition.Character)) ' Generate "_payload(pointIndex) = True". Dim payloadCell As BoundArrayAccess = statementFactory.ArrayAccess( statementFactory.Local(_methodPayload, isLValue:=False), isLValue:=True, indices:=ImmutableArray.Create(Of BoundExpression)(statementFactory.Literal(spansIndex))) Return statementFactory.Assignment(payloadCell, statementFactory.Literal(True)) End Function Private Shared Function SyntaxForSpan(statement As BoundStatement) As SyntaxNode Select Case statement.Kind Case BoundKind.IfStatement Return DirectCast(statement, BoundIfStatement).Condition.Syntax Case BoundKind.WhileStatement Return DirectCast(statement, BoundWhileStatement).Condition.Syntax Case BoundKind.ForToStatement Return DirectCast(statement, BoundForToStatement).InitialValue.Syntax Case BoundKind.ForEachStatement Return DirectCast(statement, BoundForEachStatement).Collection.Syntax Case BoundKind.DoLoopStatement Return DirectCast(statement, BoundDoLoopStatement).ConditionOpt.Syntax Case BoundKind.UsingStatement Dim usingStatement As BoundUsingStatement = DirectCast(statement, BoundUsingStatement) Return If(usingStatement.ResourceExpressionOpt, DirectCast(usingStatement, BoundNode)).Syntax Case BoundKind.SyncLockStatement Return DirectCast(statement, BoundSyncLockStatement).LockExpression.Syntax Case BoundKind.SelectStatement Return DirectCast(statement, BoundSelectStatement).ExpressionStatement.Expression.Syntax Case BoundKind.LocalDeclaration Dim initializer As BoundExpression = DirectCast(statement, BoundLocalDeclaration).InitializerOpt If initializer IsNot Nothing Then Return initializer.Syntax End If Case BoundKind.FieldInitializer, BoundKind.PropertyInitializer Dim equalsValue = TryCast(statement.Syntax, EqualsValueSyntax) If equalsValue IsNot Nothing Then Return equalsValue.Value End If Dim asNew = TryCast(statement.Syntax, AsNewClauseSyntax) If asNew IsNot Nothing Then Return asNew._newExpression End If End Select Return statement.Syntax End Function Private Shared Function GetCreatePayloadOverload(compilation As VisualBasicCompilation, overload As WellKnownMember, syntax As SyntaxNode, diagnostics As BindingDiagnosticBag) As MethodSymbol Return DirectCast(Binder.GetWellKnownTypeMember(compilation, overload, syntax, diagnostics), MethodSymbol) End Function ' If the method, property, etc. has attributes, the attributes are excluded from the span of the method definition. Private Shared Function SkipAttributes(syntax As SyntaxNode) As Text.TextSpan Select Case syntax.Kind() Case SyntaxKind.SubBlock, SyntaxKind.FunctionBlock Dim methodSyntax As MethodStatementSyntax = DirectCast(syntax, MethodBlockSyntax).SubOrFunctionStatement Return SkipAttributes(syntax, methodSyntax.AttributeLists, methodSyntax.Modifiers, methodSyntax.SubOrFunctionKeyword) Case SyntaxKind.PropertyBlock Dim propertySyntax As PropertyStatementSyntax = DirectCast(syntax, PropertyBlockSyntax).PropertyStatement Return SkipAttributes(syntax, propertySyntax.AttributeLists, propertySyntax.Modifiers, propertySyntax.PropertyKeyword) Case SyntaxKind.GetAccessorBlock, SyntaxKind.SetAccessorBlock Dim accessorSyntax As AccessorStatementSyntax = DirectCast(syntax, AccessorBlockSyntax).AccessorStatement Return SkipAttributes(syntax, accessorSyntax.AttributeLists, accessorSyntax.Modifiers, accessorSyntax.AccessorKeyword) Case SyntaxKind.ConstructorBlock Dim constructorSyntax As SubNewStatementSyntax = DirectCast(syntax, ConstructorBlockSyntax).SubNewStatement Return SkipAttributes(syntax, constructorSyntax.AttributeLists, constructorSyntax.Modifiers, constructorSyntax.SubKeyword) Case SyntaxKind.OperatorBlock Dim operatorSyntax As OperatorStatementSyntax = DirectCast(syntax, OperatorBlockSyntax).OperatorStatement Return SkipAttributes(syntax, operatorSyntax.AttributeLists, operatorSyntax.Modifiers, operatorSyntax.OperatorKeyword) End Select Return syntax.Span End Function Private Shared Function SkipAttributes(syntax As SyntaxNode, attributes As SyntaxList(Of AttributeListSyntax), modifiers As SyntaxTokenList, keyword As SyntaxToken) As Text.TextSpan Dim originalSpan As Text.TextSpan = syntax.Span If attributes.Count > 0 Then Dim startSpan As Text.TextSpan = If(modifiers.Node IsNot Nothing, modifiers.Span, keyword.Span) Return New Text.TextSpan(startSpan.Start, originalSpan.Length - (startSpan.Start - originalSpan.Start)) End If Return originalSpan End Function End Class End Namespace