' 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.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting Partial Friend Class VisualBasicTriviaFormatter Inherits AbstractTriviaFormatter Private ReadOnly _lineContinuationTrivia As SyntaxTrivia = SyntaxFactory.LineContinuationTrivia("_") Private _newLine As SyntaxTrivia Private _succeeded As Boolean = True Public Sub New(context As FormattingContext, formattingRules As ChainedFormattingRules, token1 As SyntaxToken, token2 As SyntaxToken, originalString As String, lineBreaks As Integer, spaces As Integer) MyBase.New(context, formattingRules, token1, token2, originalString, lineBreaks, spaces) End Sub Protected Overrides Function Succeeded() As Boolean Return _succeeded End Function Protected Overrides Function IsWhitespace(trivia As SyntaxTrivia) As Boolean Return trivia.RawKind = SyntaxKind.WhitespaceTrivia End Function Protected Overrides Function IsEndOfLine(trivia As SyntaxTrivia) As Boolean Return trivia.RawKind = SyntaxKind.EndOfLineTrivia End Function Protected Overrides Function IsWhitespace(ch As Char) As Boolean Return Char.IsWhiteSpace(ch) OrElse SyntaxFacts.IsWhitespace(ch) End Function Protected Overrides Function IsNewLine(ch As Char) As Boolean Return SyntaxFacts.IsNewLine(ch) End Function Protected Overrides Function CreateWhitespace(text As String) As SyntaxTrivia Return SyntaxFactory.Whitespace(text) End Function Protected Overrides Function CreateEndOfLine() As SyntaxTrivia If _newLine = Nothing Then _newLine = SyntaxFactory.EndOfLine(Context.Options.NewLine) End If Return _newLine End Function Protected Overrides Function GetLineColumnRuleBetween(trivia1 As SyntaxTrivia, existingWhitespaceBetween As LineColumnDelta, implicitLineBreak As Boolean, trivia2 As SyntaxTrivia, cancellationToken As CancellationToken) As LineColumnRule ' line continuation If trivia2.Kind = SyntaxKind.LineContinuationTrivia Then Return LineColumnRule.ForceSpacesOrUseFollowIndentation(indentation:=0) End If If IsStartOrEndOfFile(trivia1, trivia2) Then Return LineColumnRule.PreserveLinesWithAbsoluteIndentation(lines:=0, indentation:=0) End If ' :: case If trivia1.Kind = SyntaxKind.ColonTrivia AndAlso trivia2.Kind = SyntaxKind.ColonTrivia Then Return LineColumnRule.ForceSpacesOrUseDefaultIndentation(spaces:=0) End If ' : after : token If Token1.Kind = SyntaxKind.ColonToken AndAlso trivia2.Kind = SyntaxKind.ColonTrivia Then Return LineColumnRule.ForceSpacesOrUseDefaultIndentation(spaces:=0) End If ' : [token] If trivia1.Kind = SyntaxKind.ColonTrivia AndAlso trivia2.Kind = 0 AndAlso Token2.Kind <> SyntaxKind.None AndAlso Token2.Kind <> SyntaxKind.EndOfFileToken Then Return LineColumnRule.ForceSpacesOrUseDefaultIndentation(spaces:=1) End If If trivia1.Kind = SyntaxKind.ColonTrivia OrElse trivia2.Kind = SyntaxKind.ColonTrivia Then Return LineColumnRule.ForceSpacesOrUseDefaultIndentation(spaces:=1) End If ' [trivia] [whitespace] [token] case If trivia2.Kind = SyntaxKind.None Then Dim insertNewLine = Me.FormattingRules.GetAdjustNewLinesOperation(Me.Token1, Me.Token2) IsNot Nothing If insertNewLine Then Return LineColumnRule.PreserveLinesWithDefaultIndentation(lines:=0) End If Return LineColumnRule.PreserveLinesWithGivenIndentation(lines:=0) End If ' preprocessor case If SyntaxFacts.IsPreprocessorDirective(trivia2.Kind) Then ' if this is the first line of the file, don't put extra line 1 Dim firstLine = (trivia1.RawKind = SyntaxKind.None) AndAlso (Token1.Kind = SyntaxKind.None) Dim lines = If(firstLine, 0, 1) Return LineColumnRule.PreserveLinesWithAbsoluteIndentation(lines, indentation:=0) End If ' comment before a Case Statement case If trivia2.Kind = SyntaxKind.CommentTrivia AndAlso Token2.Kind = SyntaxKind.CaseKeyword AndAlso Token2.Parent.IsKind(SyntaxKind.CaseStatement) Then Return LineColumnRule.Preserve End If ' comment case If trivia2.Kind = SyntaxKind.CommentTrivia OrElse trivia2.Kind = SyntaxKind.DocumentationCommentTrivia Then ' [token] [whitespace] [trivia] case If Me.Token1.IsLastTokenOfStatementWithEndOfLine() AndAlso trivia1.Kind = SyntaxKind.None Then Return LineColumnRule.PreserveSpacesOrUseDefaultIndentation(spaces:=1) End If If trivia1.Kind = SyntaxKind.LineContinuationTrivia Then Return LineColumnRule.PreserveSpacesOrUseDefaultIndentation(spaces:=1) End If If Me.FormattingRules.GetAdjustNewLinesOperation(Me.Token1, Me.Token2) IsNot Nothing Then Return LineColumnRule.PreserveLinesWithDefaultIndentation(lines:=0) End If Return LineColumnRule.PreserveLinesWithGivenIndentation(lines:=0) End If ' skipped tokens If trivia2.Kind = SyntaxKind.SkippedTokensTrivia Then _succeeded = False End If Return LineColumnRule.Preserve End Function Protected Overrides Function ContainsImplicitLineBreak(syntaxTrivia As SyntaxTrivia) As Boolean Return False End Function Private Function IsStartOrEndOfFile(trivia1 As SyntaxTrivia, trivia2 As SyntaxTrivia) As Boolean Return (Token1.Kind = 0 OrElse Token2.Kind = 0) AndAlso (trivia1.Kind = 0 OrElse trivia2.Kind = 0) End Function Protected Overloads Overrides Function Format(lineColumn As LineColumn, trivia As SyntaxTrivia, changes As ArrayBuilder(Of SyntaxTrivia), cancellationToken As CancellationToken) As LineColumnDelta If trivia.HasStructure Then Return FormatStructuredTrivia(lineColumn, trivia, changes, cancellationToken) End If If trivia.Kind = SyntaxKind.LineContinuationTrivia Then trivia = FormatLineContinuationTrivia(trivia) End If changes.Add(trivia) Return GetLineColumnDelta(lineColumn, trivia) End Function Protected Overloads Overrides Function Format(lineColumn As LineColumn, trivia As SyntaxTrivia, changes As ArrayBuilder(Of TextChange), cancellationToken As CancellationToken) As LineColumnDelta If trivia.HasStructure Then Return FormatStructuredTrivia(lineColumn, trivia, changes, cancellationToken) End If If trivia.Kind = SyntaxKind.LineContinuationTrivia Then Dim lineContinuation = FormatLineContinuationTrivia(trivia) If trivia <> lineContinuation Then changes.Add(New TextChange(trivia.FullSpan, lineContinuation.ToFullString())) End If Return GetLineColumnDelta(lineColumn, lineContinuation) End If Return GetLineColumnDelta(lineColumn, trivia) End Function Private Function FormatLineContinuationTrivia(trivia As SyntaxTrivia) As SyntaxTrivia If trivia.ToFullString() <> _lineContinuationTrivia.ToFullString() Then Return _lineContinuationTrivia End If Return trivia End Function Private Function FormatStructuredTrivia(lineColumn As LineColumn, trivia As SyntaxTrivia, changes As ArrayBuilder(Of SyntaxTrivia), cancellationToken As CancellationToken) As LineColumnDelta If trivia.Kind = SyntaxKind.SkippedTokensTrivia Then ' don't touch anything if it contains skipped tokens _succeeded = False changes.Add(trivia) Return GetLineColumnDelta(lineColumn, trivia) End If ' TODO : make document comment to be formatted by structured trivia formatter as well. If trivia.Kind <> SyntaxKind.DocumentationCommentTrivia Then Dim result = VisualBasicStructuredTriviaFormatEngine.FormatTrivia(trivia, Me.InitialLineColumn.Column, Me.Options, Me.FormattingRules, cancellationToken) Dim formattedTrivia = SyntaxFactory.Trivia(DirectCast(result.GetFormattedRoot(cancellationToken), StructuredTriviaSyntax)) changes.Add(formattedTrivia) Return GetLineColumnDelta(lineColumn, formattedTrivia) End If Dim docComment = FormatDocumentComment(lineColumn, trivia) changes.Add(docComment) Return GetLineColumnDelta(lineColumn, docComment) End Function Private Function FormatStructuredTrivia(lineColumn As LineColumn, trivia As SyntaxTrivia, changes As ArrayBuilder(Of TextChange), cancellationToken As CancellationToken) As LineColumnDelta If trivia.Kind = SyntaxKind.SkippedTokensTrivia Then ' don't touch anything if it contains skipped tokens _succeeded = False Return GetLineColumnDelta(lineColumn, trivia) End If ' TODO : make document comment to be formatted by structured trivia formatter as well. If trivia.Kind <> SyntaxKind.DocumentationCommentTrivia Then Dim result = VisualBasicStructuredTriviaFormatEngine.FormatTrivia( trivia, Me.InitialLineColumn.Column, Me.Options, Me.FormattingRules, cancellationToken) If result.GetTextChanges(cancellationToken).Count = 0 Then Return GetLineColumnDelta(lineColumn, trivia) End If changes.AddRange(result.GetTextChanges(cancellationToken)) Dim formattedTrivia = SyntaxFactory.Trivia(DirectCast(result.GetFormattedRoot(cancellationToken), StructuredTriviaSyntax)) Return GetLineColumnDelta(lineColumn, formattedTrivia) End If Dim docComment = FormatDocumentComment(lineColumn, trivia) If docComment <> trivia Then changes.Add(New TextChange(trivia.FullSpan, docComment.ToFullString())) End If Return GetLineColumnDelta(lineColumn, docComment) End Function Private Function FormatDocumentComment(lineColumn As LineColumn, trivia As SyntaxTrivia) As SyntaxTrivia Dim indentation = Me.Context.GetBaseIndentation(trivia.SpanStart) Dim text = trivia.ToFullString() ' When the doc comment is parsed from source, even if it is only one ' line long, the end-of-line will get included into the trivia text. ' If the doc comment was parsed from a text fragment, there may not be ' an end-of-line at all. We need to trim the end before we check the ' number of line breaks in the text. #If NET Then Dim textWithoutFinalNewLine = text.TrimEnd() #Else Dim textWithoutFinalNewLine = text.TrimEnd(Nothing) #End If If Not textWithoutFinalNewLine.ContainsLineBreak() Then Return trivia End If Dim singlelineDocComments = text.ReindentStartOfXmlDocumentationComment( forceIndentation:=True, indentation:=indentation, indentationDelta:=0, useTab:=Options.UseTabs, tabSize:=Options.TabSize, newLine:=Options.NewLine) If text = singlelineDocComments Then Return trivia End If Dim singlelineDocCommentTrivia = SyntaxFactory.ParseLeadingTrivia(singlelineDocComments) Contract.ThrowIfFalse(singlelineDocCommentTrivia.Count = 1) Return singlelineDocCommentTrivia.ElementAt(0) End Function Protected Overrides Function LineContinuationFollowedByWhitespaceComment(trivia As SyntaxTrivia, nextTrivia As SyntaxTrivia) As Boolean Return trivia.Kind = SyntaxKind.LineContinuationTrivia AndAlso nextTrivia.Kind = SyntaxKind.CommentTrivia End Function Protected Overrides Function IsVisualBasicComment(trivia As SyntaxTrivia) As Boolean Return trivia.Kind = SyntaxKind.CommentTrivia End Function End Class End Namespace |