/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Features/VisualBasic/Portable/EditAndContinue/BreakpointSpans.vb
458 строк
20 KB
Tomáš Matoušek
Improve EnC analysis to handle active statements in primary constructors (#69028)
20 июл 2023, 22:50
Не верифицирован
20 июл 2023, 22:50
5f72455
Код
Авторство
О чём код?
' 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.Runtime.InteropServices Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.Text Imports System.Threading Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Friend Module BreakpointSpans Friend Function TryGetBreakpointSpan(tree As SyntaxTree, position As Integer, cancellationToken As CancellationToken, <Out> ByRef breakpointSpan As TextSpan) As Boolean Dim source = tree.GetText(cancellationToken) ' If the line is entirely whitespace, then don't set any breakpoint there. Dim line = source.Lines.GetLineFromPosition(position) If IsBlank(line) Then breakpointSpan = Nothing Return False End If ' If the user is asking for breakpoint in an inactive region, then just create a line ' breakpoint there. If tree.IsInInactiveRegion(position, cancellationToken) Then breakpointSpan = Nothing Return True End If Dim root = tree.GetRoot(cancellationToken) Return TryGetClosestBreakpointSpan(root, position, minLength:=0, breakpointSpan) End Function Private Function IsBlank(line As TextLine) As Boolean Dim text = line.ToString() For i = 0 To text.Length - 1 If Not SyntaxFacts.IsWhitespace(text(i)) Then Return False End If Next Return True End Function ''' <summary> ''' Given a syntax token determines a text span delimited by the closest applicable sequence points ''' encompassing the token. ''' </summary> ''' <remarks> ''' If the span exists it Is possible To place a breakpoint at the given position. ''' </remarks> ''' <param name="minLength"> ''' In case there are multiple breakpoint spans starting at the given <paramref name="position"/>, ''' <paramref name="minLength"/> can be used to disambiguate between them. ''' The inner-most available span whose length is at least <paramref name="minLength"/> is returned. ''' </param> Public Function TryGetClosestBreakpointSpan(root As SyntaxNode, position As Integer, minLength As Integer, <Out> ByRef span As TextSpan) As Boolean Dim node = root.FindToken(position).Parent Dim candidate As TextSpan? = Nothing While node IsNot Nothing Dim breakpointSpan = TryCreateSpanForNode(node, position) If breakpointSpan.HasValue Then If breakpointSpan.Value = New TextSpan() Then Exit While End If ' the new breakpoint span doesn't alight with the previously found breakpoint span, return the previous one: If candidate.HasValue AndAlso breakpointSpan.Value.Start <> candidate.Value.Start Then span = candidate.Value Return True End If ' The span length meets the requirement: If breakpointSpan.Value.Length >= minLength Then span = breakpointSpan.Value Return True End If candidate = breakpointSpan End If node = node.Parent End While span = candidate.GetValueOrDefault() Return candidate.HasValue End Function Private Function CreateSpan(node As SyntaxNode) As TextSpan Return TextSpan.FromBounds(node.SpanStart, node.Span.End) End Function Private Function TryCreateSpan(Of TNode As SyntaxNode)(list As SeparatedSyntaxList(Of TNode)) As TextSpan? If list.Count = 0 Then Return Nothing End If Return TextSpan.FromBounds(list.First.SpanStart, list.Last.Span.End) End Function Private Function TryCreateSpanForNode(node As SyntaxNode, position As Integer) As TextSpan? Select Case node.Kind Case SyntaxKind.VariableDeclarator, SyntaxKind.ModifiedIdentifier ' Handled by parent field or local variable declaration. Return Nothing Case SyntaxKind.FieldDeclaration Dim fieldDeclaration = DirectCast(node, FieldDeclarationSyntax) Return TryCreateSpanForVariableDeclaration(fieldDeclaration.Modifiers, fieldDeclaration.Declarators, position) Case SyntaxKind.LocalDeclarationStatement Dim localDeclaration = DirectCast(node, LocalDeclarationStatementSyntax) Return TryCreateSpanForVariableDeclaration(localDeclaration.Modifiers, localDeclaration.Declarators, position) Case SyntaxKind.PropertyStatement Return TryCreateSpanForPropertyStatement(DirectCast(node, PropertyStatementSyntax)) ' Statements that are not executable yet marked with sequence points Case SyntaxKind.IfStatement, SyntaxKind.ElseIfStatement, SyntaxKind.ElseStatement, SyntaxKind.EndIfStatement, SyntaxKind.UsingStatement, SyntaxKind.EndUsingStatement, SyntaxKind.SyncLockStatement, SyntaxKind.EndSyncLockStatement, SyntaxKind.WithStatement, SyntaxKind.EndWithStatement, SyntaxKind.SimpleDoStatement, SyntaxKind.DoWhileStatement, SyntaxKind.DoUntilStatement, SyntaxKind.SimpleLoopStatement, SyntaxKind.LoopWhileStatement, SyntaxKind.LoopUntilStatement, SyntaxKind.WhileStatement, SyntaxKind.EndWhileStatement, SyntaxKind.ForStatement, SyntaxKind.ForEachStatement, SyntaxKind.NextStatement, SyntaxKind.SelectStatement, SyntaxKind.CaseStatement, SyntaxKind.CaseElseStatement, SyntaxKind.EndSelectStatement, SyntaxKind.TryStatement, SyntaxKind.CatchStatement, SyntaxKind.FinallyStatement, SyntaxKind.EndTryStatement, SyntaxKind.EndSubStatement, SyntaxKind.EndFunctionStatement, SyntaxKind.EndOperatorStatement, SyntaxKind.EndGetStatement, SyntaxKind.EndSetStatement, SyntaxKind.EndAddHandlerStatement, SyntaxKind.EndRemoveHandlerStatement, SyntaxKind.EndRaiseEventStatement, SyntaxKind.FunctionLambdaHeader, SyntaxKind.SubLambdaHeader Return CreateSpan(node) Case SyntaxKind.SubStatement, SyntaxKind.SubNewStatement, SyntaxKind.FunctionStatement, SyntaxKind.OperatorStatement, SyntaxKind.GetAccessorStatement, SyntaxKind.SetAccessorStatement, SyntaxKind.AddHandlerAccessorStatement, SyntaxKind.RemoveHandlerAccessorStatement, SyntaxKind.RaiseEventAccessorStatement Return CreateSpanForMethodBase(DirectCast(node, MethodBaseSyntax)) Case SyntaxKind.SingleLineIfStatement Dim asSingleLine = DirectCast(node, SingleLineIfStatementSyntax) If position >= asSingleLine.IfKeyword.SpanStart AndAlso position < asSingleLine.ThenKeyword.Span.End Then Return TextSpan.FromBounds(asSingleLine.IfKeyword.SpanStart, asSingleLine.ThenKeyword.Span.End) Else Return CreateSpan(node) End If Case SyntaxKind.SingleLineElseClause Dim asSingleLineElse = DirectCast(node, SingleLineElseClauseSyntax) Return asSingleLineElse.ElseKeyword.Span Case SyntaxKind.FunctionAggregation Return TryCreateSpanForFunctionAggregation(DirectCast(node, FunctionAggregationSyntax)) Case SyntaxKind.SingleLineFunctionLambdaExpression, SyntaxKind.SingleLineSubLambdaExpression Return CreateSpan(node) Case SyntaxKind.SelectClause Return TryCreateSpanForSelectClause(DirectCast(node, SelectClauseSyntax)) Case SyntaxKind.WhereClause Return TryCreateSpanForWhereClause(DirectCast(node, WhereClauseSyntax)) Case SyntaxKind.CollectionRangeVariable Return TryCreateSpanForCollectionRangeVariable(DirectCast(node, CollectionRangeVariableSyntax)) Case SyntaxKind.LetClause Return TryCreateSpanForLetClause(DirectCast(node, LetClauseSyntax), position) Case SyntaxKind.GroupByClause Return TryCreateSpanForGroupByClause(DirectCast(node, GroupByClauseSyntax), position) Case SyntaxKind.SkipWhileClause, SyntaxKind.TakeWhileClause Return TryCreateSpanForPartitionWhileClauseSyntax(DirectCast(node, PartitionWhileClauseSyntax)) Case SyntaxKind.AscendingOrdering, SyntaxKind.DescendingOrdering Return TryCreateSpanForOrderingSyntax(DirectCast(node, OrderingSyntax)) Case SyntaxKind.OrderByClause Return TryCreateSpanForOrderByClause(DirectCast(node, OrderByClauseSyntax), position) Case SyntaxKind.FromClause Return TryCreateSpanForFromClause(DirectCast(node, FromClauseSyntax), position) Case Else Dim executableStatement = TryCast(node, ExecutableStatementSyntax) If executableStatement IsNot Nothing Then Return CreateSpan(node) End If Dim expression = TryCast(node, ExpressionSyntax) If expression IsNot Nothing Then Return TryCreateSpanForExpression(expression) End If Return Nothing End Select End Function Private Function CreateSpanForMethodBase(methodBase As MethodBaseSyntax) As TextSpan If methodBase.Modifiers.Count = 0 Then Return TextSpan.FromBounds(methodBase.DeclarationKeyword.SpanStart, methodBase.Span.End) End If Return TextSpan.FromBounds(methodBase.Modifiers.First().SpanStart, methodBase.Span.End) End Function Private Function TryCreateSpanForPropertyStatement(node As PropertyStatementSyntax) As TextSpan? If node.Parent.IsKind(SyntaxKind.PropertyBlock) Then ' not an auto-property: Return Nothing End If If node.Initializer IsNot Nothing Then Return TextSpan.FromBounds(node.Identifier.Span.Start, node.Initializer.Span.End) End If If node.AsClause IsNot Nothing AndAlso node.AsClause.IsKind(SyntaxKind.AsNewClause) Then Return TextSpan.FromBounds(node.Identifier.Span.Start, node.AsClause.Span.End) End If Return Nothing End Function Private Function TryCreateSpanForVariableDeclaration(modifiers As SyntaxTokenList, declarators As SeparatedSyntaxList(Of VariableDeclaratorSyntax), position As Integer) As TextSpan? If declarators.Count = 0 Then Return Nothing End If If modifiers.Any(SyntaxKind.ConstKeyword) Then Return New TextSpan() End If Dim name = FindClosestNameWithInitializer(declarators, position) If name Is Nothing Then Return New TextSpan() End If If name.ArrayBounds IsNot Nothing OrElse DirectCast(name.Parent, VariableDeclaratorSyntax).Names.Count > 1 Then Return CreateSpan(name) Else Return CreateSpan(name.Parent) End If End Function Private Function FindClosestNameWithInitializer(declarators As SeparatedSyntaxList(Of VariableDeclaratorSyntax), position As Integer) As ModifiedIdentifierSyntax Return FindClosestNode(declarators, position, Function(declarator) If declarator.HasInitializer Then Return declarator.Names(GetItemIndexByPosition(declarator.Names, position)) End If Return FindClosestNode(declarator.Names, position, Function(idf) Return If(idf.ArrayBounds IsNot Nothing, idf, Nothing) End Function) End Function) End Function Private Function FindClosestNode(Of TListNode As SyntaxNode, TResult As SyntaxNode)(nodes As SeparatedSyntaxList(Of TListNode), position As Integer, predicate As Func(Of TListNode, TResult)) As TResult Dim d = GetItemIndexByPosition(nodes, position) Dim i = 0 Do Dim left = d - i Dim right = d + i If left < 0 AndAlso right >= nodes.Count Then Return Nothing End If If left >= 0 Then Dim result = predicate(nodes(left)) If result IsNot Nothing Then Return result End If End If If right < nodes.Count Then Dim result = predicate(nodes(right)) If result IsNot Nothing Then Return result End If End If i += 1 Loop End Function Private Function GetItemIndexByPosition(Of TNode As SyntaxNode)(list As SeparatedSyntaxList(Of TNode), position As Integer) As Integer For i = list.SeparatorCount - 1 To 0 Step -1 If position > list.GetSeparator(i).SpanStart Then Return i + 1 End If Next Return 0 End Function Private Function TryCreateSpanForFromClause(fromClause As FromClauseSyntax, position As Integer) As TextSpan? Dim query = DirectCast(fromClause.Parent, QueryExpressionSyntax) ' If it's not the first from clause, then you can set the breakpoint on the first ' variable. If query.Clauses.First() IsNot fromClause AndAlso fromClause.Variables.Any() Then Return TryCreateSpanForNode(fromClause.Variables.First(), position) End If ' If it is the first from clause, you can only set the breakpoint on the second or ' higher variable. If query.Clauses.First() Is fromClause AndAlso fromClause.Variables.Count > 1 Then Return TryCreateSpanForNode(fromClause.Variables(1), position) End If Return Nothing End Function Private Function TryCreateSpanForFunctionAggregation(functionAggregation As FunctionAggregationSyntax) As TextSpan? If functionAggregation.Argument IsNot Nothing Then Return CreateSpan(functionAggregation.Argument) End If Return Nothing End Function Private Function TryCreateSpanForOrderByClause(orderByClause As OrderByClauseSyntax, position As Integer) As TextSpan? If orderByClause.Orderings.Any() Then Return TryCreateSpanForNode(orderByClause.Orderings.First(), position) End If Return Nothing End Function Private Function TryCreateSpanForOrderingSyntax(orderingSyntax As OrderingSyntax) As TextSpan? Return CreateSpan(orderingSyntax.Expression) End Function Private Function TryCreateSpanForPartitionWhileClauseSyntax(partitionWhileClause As PartitionWhileClauseSyntax) As TextSpan? Return CreateSpan(partitionWhileClause.Condition) End Function Private Function TryCreateSpanForCollectionRangeVariable(collectionRangeVariable As CollectionRangeVariableSyntax) As TextSpan? If collectionRangeVariable.Parent.Kind = SyntaxKind.FromClause Then Dim fromClause = DirectCast(collectionRangeVariable.Parent, FromClauseSyntax) Dim query = DirectCast(fromClause.Parent, QueryExpressionSyntax) ' We can break on this expression if we're not the first clause in a ' query, or if the range variable it not the first range variable in the ' list. If query.Clauses.First() IsNot fromClause OrElse fromClause.Variables.IndexOf(collectionRangeVariable) <> 0 Then Return CreateSpan(collectionRangeVariable.Expression) End If End If Return Nothing End Function Private Function TryCreateSpanForWhereClause(clause As WhereClauseSyntax) As TextSpan? Return CreateSpan(clause.Condition) End Function Private Function TryCreateSpanForGroupByClause(clause As GroupByClauseSyntax, position As Integer) As TextSpan? If position < clause.ByKeyword.SpanStart Then If clause.Items.Count = 1 Then Return CreateSpan(clause.Items.Single.Expression) End If Return TryCreateSpan(clause.Items) End If If clause.Keys.Count = 0 Then Return Nothing End If If position >= clause.Keys.First.SpanStart AndAlso position < clause.IntoKeyword.SpanStart Then If clause.Keys.Count = 1 Then Return CreateSpan(clause.Keys.Single.Expression) End If Return TryCreateSpan(clause.Keys) End If Return TextSpan.FromBounds(clause.Keys.First.SpanStart, clause.Span.End) End Function Private Function TryCreateSpanForSelectClause(clause As SelectClauseSyntax) As TextSpan? If clause.Variables.Count = 1 Then Return CreateSpan(clause.Variables.Single.Expression) End If Return TryCreateSpan(clause.Variables) End Function Private Function TryCreateSpanForLetClause(clause As LetClauseSyntax, position As Integer) As TextSpan? Return clause.Variables(GetItemIndexByPosition(clause.Variables, position)).Expression.Span End Function Private Function TryCreateSpanForExpression(expression As ExpressionSyntax) As TextSpan? If IsBreakableExpression(expression) Then Return CreateSpan(expression) End If Return Nothing End Function Private Function IsBreakableExpression(expression As ExpressionSyntax) As Boolean If expression Is Nothing OrElse expression.Parent Is Nothing Then Return False End If Select Case expression.Parent.Kind Case SyntaxKind.JoinCondition Dim joinCondition = DirectCast(expression.Parent, JoinConditionSyntax) Return expression Is joinCondition.Left OrElse expression Is joinCondition.Right Case SyntaxKind.SingleLineFunctionLambdaExpression Dim lambda = DirectCast(expression.Parent, SingleLineLambdaExpressionSyntax) Return expression Is lambda.Body End Select Return False End Function End Module End Namespace