|
' 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 Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Formatting.Rules
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting
Friend Class ElasticTriviaFormattingRule
Inherits BaseFormattingRule
Friend Const Name As String = "VisualBasic Elastic Trivia Formatting Rule"
Public Sub New()
End Sub
Public Overrides Sub AddSuppressOperationsSlow(list As ArrayBuilder(Of SuppressOperation), node As SyntaxNode, ByRef nextOperation As NextSuppressOperationAction)
nextOperation.Invoke()
End Sub
Public Overrides Sub AddIndentBlockOperationsSlow(list As List(Of IndentBlockOperation), node As SyntaxNode, ByRef nextOperation As NextIndentBlockOperationAction)
nextOperation.Invoke()
If node.Kind = SyntaxKind.ObjectMemberInitializer Then
Dim initializer = DirectCast(node, ObjectMemberInitializerSyntax)
If initializer.GetLeadingTrivia().HasAnyWhitespaceElasticTrivia() Then
AddIndentBlockOperation(list,
initializer.OpenBraceToken,
initializer.CloseBraceToken.GetPreviousToken(),
[option]:=IndentBlockOption.RelativePosition)
list.Add(FormattingOperations.CreateIndentBlockOperation(
initializer.CloseBraceToken, initializer.CloseBraceToken,
indentationDelta:=0,
[option]:=IndentBlockOption.RelativePosition))
End If
End If
If node.Kind = SyntaxKind.ObjectCollectionInitializer Then
Dim collectionInitializer = DirectCast(node, ObjectCollectionInitializerSyntax)
If collectionInitializer.GetLeadingTrivia().HasAnyWhitespaceElasticTrivia() Then
Dim initializer = collectionInitializer.Initializer
AddIndentBlockOperation(list,
initializer.OpenBraceToken,
initializer.CloseBraceToken.GetPreviousToken(),
[option]:=IndentBlockOption.RelativePosition)
list.Add(FormattingOperations.CreateIndentBlockOperation(
initializer.CloseBraceToken, initializer.CloseBraceToken,
indentationDelta:=0,
[option]:=IndentBlockOption.RelativePosition))
End If
End If
End Sub
Public Overrides Sub AddAlignTokensOperationsSlow(list As List(Of AlignTokensOperation),
node As SyntaxNode,
ByRef nextOperation As NextAlignTokensOperationAction)
nextOperation.Invoke()
If node.Kind = SyntaxKind.ObjectMemberInitializer Then
Dim initializer = DirectCast(node, ObjectMemberInitializerSyntax)
If initializer.GetLeadingTrivia().HasAnyWhitespaceElasticTrivia() Then
list.Add(New AlignTokensOperation(
initializer.WithKeyword,
SpecializedCollections.SingletonEnumerable(initializer.CloseBraceToken),
[option]:=AlignTokensOption.AlignIndentationOfTokensToFirstTokenOfBaseTokenLine))
End If
End If
If node.Kind = SyntaxKind.ObjectCollectionInitializer Then
Dim collectionInitializer = DirectCast(node, ObjectCollectionInitializerSyntax)
If collectionInitializer.GetLeadingTrivia().HasAnyWhitespaceElasticTrivia() Then
list.Add(New AlignTokensOperation(
collectionInitializer.FromKeyword,
SpecializedCollections.SingletonEnumerable(collectionInitializer.Initializer.CloseBraceToken),
[option]:=AlignTokensOption.AlignIndentationOfTokensToFirstTokenOfBaseTokenLine))
End If
End If
End Sub
Public Overrides Function GetAdjustSpacesOperationSlow(ByRef previousToken As SyntaxToken, ByRef currentToken As SyntaxToken, ByRef nextOperation As NextGetAdjustSpacesOperation) As AdjustSpacesOperation
' if it doesn't have elastic trivia, pass it through
If Not CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(previousToken, currentToken) Then
Return nextOperation.Invoke(previousToken, currentToken)
End If
' if it has one, check whether there is a forced one
Dim operation = nextOperation.Invoke(previousToken, currentToken)
If operation IsNot Nothing AndAlso operation.Option = AdjustSpacesOption.ForceSpaces Then
Return operation
End If
' remove blank lines between parameter lists and implements clauses
If currentToken.Kind = SyntaxKind.ImplementsKeyword AndAlso
(previousToken.GetAncestor(Of MethodStatementSyntax)() IsNot Nothing OrElse
previousToken.GetAncestor(Of PropertyStatementSyntax)() IsNot Nothing OrElse
previousToken.GetAncestor(Of EventStatementSyntax)() IsNot Nothing) Then
Return FormattingOperations.CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces)
End If
' handle comma separated lists in implements clauses
If previousToken.GetAncestor(Of ImplementsClauseSyntax)() IsNot Nothing AndAlso currentToken.Kind = SyntaxKind.CommaToken Then
Return FormattingOperations.CreateAdjustSpacesOperation(0, AdjustSpacesOption.ForceSpaces)
End If
If currentToken.Kind = SyntaxKind.OpenBraceToken AndAlso
currentToken.Parent.Kind = SyntaxKind.CollectionInitializer AndAlso
currentToken.Parent.Parent.Kind = SyntaxKind.ObjectCollectionInitializer Then
Return New AdjustSpacesOperation(1,
[option]:=AdjustSpacesOption.ForceSpaces)
End If
Return operation
End Function
Public Overrides Function GetAdjustNewLinesOperationSlow(
ByRef previousToken As SyntaxToken,
ByRef currentToken As SyntaxToken,
ByRef nextOperation As NextGetAdjustNewLinesOperation) As AdjustNewLinesOperation
' if it doesn't have elastic trivia, pass it through
If Not CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(previousToken, currentToken) Then
Return nextOperation.Invoke(previousToken, currentToken)
End If
' if it has one, check whether there is a forced one
Dim operation = nextOperation.Invoke(previousToken, currentToken)
If operation IsNot Nothing AndAlso operation.Option = AdjustNewLinesOption.ForceLines Then
Return operation
End If
If currentToken.Kind = SyntaxKind.DotToken AndAlso
currentToken.Parent.Kind = SyntaxKind.NamedFieldInitializer Then
Return New AdjustNewLinesOperation(line:=1,
[option]:=AdjustNewLinesOption.ForceLines)
End If
If previousToken.Kind = SyntaxKind.OpenBraceToken AndAlso
previousToken.Parent.Kind = SyntaxKind.CollectionInitializer AndAlso
previousToken.Parent.Parent.Kind = SyntaxKind.ObjectCollectionInitializer Then
Return New AdjustNewLinesOperation(line:=1,
[option]:=AdjustNewLinesOption.ForceLines)
End If
If previousToken.Kind = SyntaxKind.CommaToken AndAlso
previousToken.Parent.Kind = SyntaxKind.CollectionInitializer AndAlso
previousToken.Parent.Parent.Kind = SyntaxKind.ObjectCollectionInitializer Then
Return New AdjustNewLinesOperation(line:=1,
[option]:=AdjustNewLinesOption.ForceLines)
End If
If currentToken.Kind = SyntaxKind.OpenBraceToken AndAlso
currentToken.Parent.Kind = SyntaxKind.CollectionInitializer AndAlso
currentToken.Parent.Parent.Kind = SyntaxKind.CollectionInitializer Then
Return New AdjustNewLinesOperation(line:=1,
[option]:=AdjustNewLinesOption.ForceLines)
End If
If currentToken.Kind = SyntaxKind.CloseBraceToken Then
If currentToken.Parent.Kind = SyntaxKind.ObjectMemberInitializer Then
Return New AdjustNewLinesOperation(line:=1,
[option]:=AdjustNewLinesOption.ForceLines)
End If
If currentToken.Parent.Kind = SyntaxKind.CollectionInitializer AndAlso
currentToken.Parent.Parent.Kind = SyntaxKind.ObjectCollectionInitializer Then
Return New AdjustNewLinesOperation(line:=1,
[option]:=AdjustNewLinesOption.ForceLines)
End If
End If
' put attributes in its own line if it is top level attribute
Dim attributeNode = TryCast(previousToken.Parent, AttributeListSyntax)
If attributeNode IsNot Nothing AndAlso TypeOf attributeNode.Parent Is StatementSyntax AndAlso
attributeNode.GreaterThanToken = previousToken AndAlso currentToken.Kind <> SyntaxKind.LessThanToken Then
Return FormattingOperations.CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.ForceLines)
End If
If Not previousToken.IsLastTokenOfStatement() Then
Return operation
End If
' The previous token may end a statement, but it could be in a lambda inside parens.
If currentToken.Kind = SyntaxKind.CloseParenToken AndAlso
TypeOf currentToken.Parent Is ParenthesizedExpressionSyntax Then
Return operation
End If
If AfterLastInheritsOrImplements(previousToken, currentToken) Then
If Not TypeOf currentToken.Parent Is EndBlockStatementSyntax Then
Return FormattingOperations.CreateAdjustNewLinesOperation(2, AdjustNewLinesOption.ForceLines)
End If
End If
If AfterLastImportStatement(previousToken, currentToken) Then
Return FormattingOperations.CreateAdjustNewLinesOperation(2, AdjustNewLinesOption.ForceLines)
End If
Dim lines = LineBreaksAfter(previousToken, currentToken)
If Not lines.HasValue Then
If TypeOf previousToken.Parent Is XmlNodeSyntax Then
' make sure next statement starts on its own line if previous statement ends with xml literals
Return FormattingOperations.CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines)
End If
Return CreateAdjustNewLinesOperation(Math.Max(If(operation Is Nothing, 1, operation.Line), 0), AdjustNewLinesOption.PreserveLines)
End If
If lines = 0 Then
Return CreateAdjustNewLinesOperation(0, AdjustNewLinesOption.PreserveLines)
End If
Return CreateAdjustNewLinesOperation(lines.Value, AdjustNewLinesOption.ForceLines)
End Function
Private Shared Function AfterLastImportStatement(token As SyntaxToken, nextToken As SyntaxToken) As Boolean
' in between two imports
If nextToken.Kind = SyntaxKind.ImportsKeyword Then
Return False
End If
' current one is not import statement
If Not TypeOf token.Parent Is NameSyntax Then
Return False
End If
Dim [imports] = token.GetAncestor(Of ImportsStatementSyntax)()
If [imports] Is Nothing Then
Return False
End If
Return True
End Function
Private Shared Function AfterLastInheritsOrImplements(token As SyntaxToken, nextToken As SyntaxToken) As Boolean
Dim inheritsOrImplements = token.GetAncestor(Of InheritsOrImplementsStatementSyntax)()
Dim nextInheritsOrImplements = nextToken.GetAncestor(Of InheritsOrImplementsStatementSyntax)()
Return inheritsOrImplements IsNot Nothing AndAlso nextInheritsOrImplements Is Nothing
End Function
Private Shared Function IsBeginStatement(Of TStatement As StatementSyntax, TBlock As StatementSyntax)(node As StatementSyntax) As Boolean
Return TryCast(node, TStatement) IsNot Nothing AndAlso TryCast(node.Parent, TBlock) IsNot Nothing
End Function
Private Shared Function IsEndBlockStatement(node As StatementSyntax) As Boolean
Return TryCast(node, EndBlockStatementSyntax) IsNot Nothing OrElse
TryCast(node, LoopStatementSyntax) IsNot Nothing OrElse
TryCast(node, NextStatementSyntax) IsNot Nothing
End Function
Private Shared Function LineBreaksAfter(previousToken As SyntaxToken, currentToken As SyntaxToken) As Integer?
If currentToken.Kind = SyntaxKind.None OrElse
previousToken.Kind = SyntaxKind.None Then
Return 0
End If
Dim previousStatement = previousToken.GetAncestor(Of StatementSyntax)()
Dim currentStatement = currentToken.GetAncestor(Of StatementSyntax)()
If previousStatement Is Nothing OrElse currentStatement Is Nothing Then
Return Nothing
End If
If TopLevelStatement(previousStatement) AndAlso Not TopLevelStatement(currentStatement) Then
Return GetActualLines(previousToken, currentToken, 1)
End If
' Early out of accessors, we don't force more lines between them.
If previousStatement.Kind = SyntaxKind.EndSetStatement OrElse
previousStatement.Kind = SyntaxKind.EndGetStatement OrElse
previousStatement.Kind = SyntaxKind.EndAddHandlerStatement OrElse
previousStatement.Kind = SyntaxKind.EndRemoveHandlerStatement OrElse
previousStatement.Kind = SyntaxKind.EndRaiseEventStatement Then
Return Nothing
End If
' Blank line after an end block, unless it's followed by another end or an else
If IsEndBlockStatement(previousStatement) Then
If IsEndBlockStatement(currentStatement) OrElse
currentStatement.Kind = SyntaxKind.ElseIfStatement OrElse
currentStatement.Kind = SyntaxKind.ElseStatement Then
Return GetActualLines(previousToken, currentToken, 1)
Else
Return GetActualLines(previousToken, currentToken, 2, 1)
End If
End If
' Blank line _before_ a block, unless it's the first thing in a type.
If IsBeginStatement(Of MethodStatementSyntax, MethodBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of SubNewStatementSyntax, ConstructorBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of OperatorStatementSyntax, OperatorBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of PropertyStatementSyntax, PropertyBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of EventStatementSyntax, EventBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of TypeStatementSyntax, TypeBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of EnumStatementSyntax, EnumBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of NamespaceStatementSyntax, NamespaceBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of DoStatementSyntax, DoLoopBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of ForStatementSyntax, ForOrForEachBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of ForEachStatementSyntax, ForOrForEachBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of IfStatementSyntax, MultiLineIfBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of SelectStatementSyntax, SelectBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of SyncLockStatementSyntax, SyncLockBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of TryStatementSyntax, TryBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of UsingStatementSyntax, UsingBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of WhileStatementSyntax, WhileBlockSyntax)(currentStatement) OrElse
IsBeginStatement(Of WithStatementSyntax, WithBlockSyntax)(currentStatement) Then
If TypeOf previousStatement Is NamespaceStatementSyntax OrElse
TypeOf previousStatement Is TypeStatementSyntax OrElse
TypeOf previousStatement Is IfStatementSyntax Then
Return GetActualLines(previousToken, currentToken, 1)
Else
Return GetActualLines(previousToken, currentToken, 2, 1)
End If
End If
Return Nothing
End Function
Private Shared Function GetActualLines(token1 As SyntaxToken, token2 As SyntaxToken, lines As Integer, Optional leadingBlankLines As Integer = 0) As Integer
If leadingBlankLines = 0 Then
Return Math.Max(lines, 0)
End If
' see whether first non whitespace trivia after previous member is comment or not
Dim list = token1.TrailingTrivia.Concat(token2.LeadingTrivia)
Dim firstNonWhitespaceTrivia = list.FirstOrDefault(Function(t) Not t.IsWhitespaceOrEndOfLine())
If firstNonWhitespaceTrivia.IsKind(SyntaxKind.CommentTrivia, SyntaxKind.DocumentationCommentTrivia) Then
Dim totalLines = GetNumberOfLines(list)
Dim blankLines = GetNumberOfLines(list.TakeWhile(Function(t) t <> firstNonWhitespaceTrivia))
If totalLines < lines Then
Dim afterCommentWithBlank = (totalLines - blankLines) + leadingBlankLines
Return Math.Max(If(lines > afterCommentWithBlank, lines, afterCommentWithBlank), 0)
End If
If blankLines < leadingBlankLines Then
Return Math.Max(totalLines - blankLines + leadingBlankLines, 0)
End If
Return Math.Max(totalLines, 0)
End If
Return Math.Max(lines, 0)
End Function
Private Shared Function GetNumberOfLines(list As IEnumerable(Of SyntaxTrivia)) As Integer
Return list.Sum(Function(t) t.ToFullString().Replace(vbCrLf, vbCr).OfType(Of Char).Count(Function(c) SyntaxFacts.IsNewLine(c)))
End Function
Private Shared Function TopLevelStatement(statement As StatementSyntax) As Boolean
Return TypeOf statement Is MethodStatementSyntax OrElse
TypeOf statement Is SubNewStatementSyntax OrElse
TypeOf statement Is LambdaHeaderSyntax OrElse
TypeOf statement Is OperatorStatementSyntax OrElse
TypeOf statement Is PropertyStatementSyntax OrElse
TypeOf statement Is EventStatementSyntax OrElse
TypeOf statement Is TypeStatementSyntax OrElse
TypeOf statement Is EnumStatementSyntax OrElse
TypeOf statement Is NamespaceStatementSyntax
End Function
End Class
End Namespace
|