File: Tests\TestWriter.vb
Web Access
Project: src\src\Tools\Source\CompilerGeneratorTools\Source\VisualBasicSyntaxGenerator\VisualBasicSyntaxGenerator.vbproj (VisualBasicSyntaxGenerator)
' 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.
 
'-----------------------------------------------------------------------------------------------------------
' This is the code that actually outputs the VB code that defines the tree. It is passed a read and validated
' ParseTree, and outputs the code to defined the node classes for that tree, and also additional data
' structures like the kinds, visitor, etc.
'-----------------------------------------------------------------------------------------------------------
 
Imports System.IO
 
Public Class TestWriter
    Inherits WriteUtils
 
    Private ReadOnly _checksum As String
    Private _writer As TextWriter    'output is sent here.
    Private Const s_externalSourceDirectiveString As String = "ExternalSourceDirective"
 
    ' Initialize the class with the parse tree to write.
    Public Sub New(parseTree As ParseTree, checksum As String)
        MyBase.New(parseTree)
        _checksum = checksum
    End Sub
 
    ' Write out the code defining the tree to the give file.
    Public Sub WriteTestCode(writer As TextWriter)
        _writer = writer
 
        GenerateFile()
    End Sub
 
    Private Sub GenerateFile()
        _writer.WriteLine("' Tests for parse trees.")
        _writer.WriteLine("' DO NOT HAND EDIT")
        _writer.WriteLine()
 
        GenerateNamespace()
    End Sub
 
    Private Sub GenerateNamespace()
        _writer.WriteLine()
        _writer.WriteLine("Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests")
        _writer.WriteLine()
 
        _writer.WriteLine("Partial Public Class GeneratedTests")
        _writer.WriteLine()
 
        _writer.WriteLine("#region ""Green Factory Calls""")
        GenerateFactoryCalls(True)
        _writer.WriteLine("#end region")
        _writer.WriteLine()
 
        _writer.WriteLine("#region ""Green Factory Tests""")
        GenerateFactoryCallTests(True)
        _writer.WriteLine("#end region")
        _writer.WriteLine()
 
        _writer.WriteLine("#region ""Green Rewriter Tests""")
        GenerateRewriterTests(True)
        _writer.WriteLine("#end region")
        _writer.WriteLine()
 
        _writer.WriteLine("#region ""Green Visitor Tests""")
        GenerateVisitorTests(True)
        _writer.WriteLine("#end region")
        _writer.WriteLine()
 
        _writer.WriteLine("#region ""Red Factory Calls""")
        GenerateFactoryCalls(False)
        _writer.WriteLine("#end region")
        _writer.WriteLine()
 
        _writer.WriteLine("#region ""Red Factory Tests""")
        GenerateFactoryCallTests(False)
        _writer.WriteLine("#end region")
        _writer.WriteLine()
 
        _writer.WriteLine("#region ""Red Rewriter Tests""")
        GenerateRewriterTests(False)
        _writer.WriteLine("#end region")
        _writer.WriteLine()
 
        _writer.WriteLine("End Class")
        _writer.WriteLine("End Namespace")
    End Sub
 
    Private Sub GenerateFactoryCalls(isGreen As Boolean)
        For Each nodeStructure In _parseTree.NodeStructures.Values
            If Not nodeStructure.Abstract AndAlso Not nodeStructure.NoFactory Then
 
                If Not nodeStructure.Name = "KeywordSyntax" AndAlso Not nodeStructure.Name = "PunctuationSyntax" Then
                    GenerateFactoryCall(isGreen, nodeStructure)
                End If
            End If
        Next
    End Sub
 
    Private Sub GenerateFactoryCall(isGreen As Boolean, nodeStructure As ParseNodeStructure)
        For Each kind In nodeStructure.NodeKinds
            GenerateFactoryCall(isGreen, nodeStructure, kind)
        Next
 
        'If nodeStructure.NodeKinds.Count > 1 Then
        '    GenerateFactoryCall(isGreen, nodeStructure, Nothing)
        'End If
 
    End Sub
 
    Private Sub GenerateFactoryCall(isGreen As Boolean, nodeStructure As ParseNodeStructure, nodeKind As ParseNodeKind)
 
        If nodeKind.Name = "AttributeTarget" AndAlso Not isGreen Then
            Dim x = 0
        End If
 
        If nodeKind.Name.Contains(s_externalSourceDirectiveString) Then
            Return ' check for fix
        End If
 
        Dim namespacePrefix As String = If(isGreen, "InternalSyntax.", String.Empty)
 
        _writer.Write("        Private Shared Function ")
 
        Dim functionName As String = If(nodeKind Is Nothing, FactoryName(nodeStructure), FactoryName(nodeKind))
 
        If isGreen Then
            _writer.Write("GenerateGreen" + functionName)
        Else
            _writer.Write("GenerateRed" + functionName)
        End If
 
        If isGreen Then
            _writer.WriteLine("() As " + namespacePrefix + nodeStructure.Name)
        Else
 
            If nodeStructure.IsToken Then
                _writer.WriteLine("() As SyntaxToken")
            ElseIf nodeStructure.IsTrivia Then
                _writer.WriteLine("() As SyntaxTrivia")
            Else
                _writer.WriteLine("() As {0}", StructureTypeName(nodeStructure))
            End If
        End If
 
        Dim first As Boolean = True
 
        Dim callTokens As List(Of String) = New List(Of String)()
        Dim anePositions As List(Of Integer) = New List(Of Integer)()
        Dim aePositions As List(Of Integer) = New List(Of Integer)()
        Dim KindNonePositions As List(Of Integer) = New List(Of Integer)()
 
        Dim currentLine = 1
 
        If nodeKind Is Nothing Then
            callTokens.Add(namespacePrefix + "SyntaxFactory." + nodeStructure.Name + "(")
        Else
            callTokens.Add(namespacePrefix + "SyntaxFactory." + nodeKind.Name + "(")
        End If
 
        If nodeStructure.IsToken Then
 
            If isGreen Then
                If nodeStructure.IsTerminal Then
                    callTokens.Add("String.Empty")
                    first = False
                End If
            Else
                If Not first Then callTokens.Add(", ")
                first = False
 
                callTokens.Add(namespacePrefix + "SyntaxFactory.TriviaList(SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "" ""))")
 
                'If nodeStructure.Name.Contains("Xml") Then
                '    callTokens.Add("String.Empty")
                '    first = False
                'End If
 
                If nodeKind.Name.EndsWith("LiteralToken", StringComparison.Ordinal) OrElse
                   nodeKind.Name.EndsWith("XmlNameToken", StringComparison.Ordinal) OrElse
                   nodeKind.Name.EndsWith("DocumentationCommentLineBreakToken", StringComparison.Ordinal) OrElse
                   nodeKind.Name = "InterpolatedStringTextToken" _
                Then
                    If Not first Then callTokens.Add(", ")
                    callTokens.Add("String.Empty")
                    first = False
                End If
 
            End If
 
            Dim fields = GetAllFieldsOfStructure(nodeStructure)
 
            For Each field In fields
 
                If Not first Then callTokens.Add(", ")
                first = False
 
                Dim fieldType = FieldTypeRef(field)
                callTokens.Add(GetInitValueForType(fieldType))
 
            Next
 
            If Not first Then callTokens.Add(", ")
            first = False
 
            If isGreen Then
                callTokens.Add(namespacePrefix + "SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "" ""), ")
                callTokens.Add(namespacePrefix + "SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "" ""))")
            Else
                callTokens.Add(namespacePrefix + "SyntaxFactory.TriviaList(SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "" "")))")
            End If
 
            If isGreen Then
                _writer.Write("            return ")
                callTokens.ForEach(AddressOf _writer.Write)
                _writer.WriteLine()
            Else
                _writer.WriteLine("            Dim exceptionTest as boolean = false")
 
                For exceptionChecks = 0 To anePositions.Count - 1
 
                    _writer.WriteLine("            Try")
 
                    For i = 0 To callTokens.Count - 1
                        If (i <> anePositions(exceptionChecks)) Then
                            _writer.Write(callTokens(i))
                        Else
                            _writer.Write("Nothing")
                        End If
                    Next
 
                    _writer.WriteLine("            catch e as ArgumentNullException")
                    _writer.WriteLine("            exceptionTest = true")
                    _writer.WriteLine("            End Try")
                    _writer.WriteLine("            Debug.Assert(exceptionTest)")
                    _writer.WriteLine("            exceptionTest = false")
                    _writer.WriteLine()
                Next
 
                ' quick hack to cover more code in keyword factories ...
                If nodeStructure.IsTerminal AndAlso Not nodeStructure.IsTrivia AndAlso nodeStructure.Name = "KeywordSyntax" Then
 
                    _writer.Write("            Dim node1 = ")
                    For i = 0 To callTokens.Count - 1
                        _writer.Write(callTokens(i))
                        If i = 0 Then _writer.Write("String.Empty, ")
                    Next i
                    _writer.WriteLine()
 
                    _writer.Write("            dim node2 = ")
                    callTokens.ForEach(AddressOf _writer.Write)
                    _writer.WriteLine()
 
                    _writer.WriteLine("            Debug.Assert(node1.GetText() = String.Empty)")
                    _writer.WriteLine("            Debug.Assert(node2.GetText() <> String.Empty)")
                    _writer.WriteLine()
 
                    ' make parameter = nothing to cause exceptions
                    _writer.WriteLine("            Try")
                    _writer.WriteLine("            exceptionTest = false")
 
                    For i = 0 To callTokens.Count - 1
                        _writer.Write(callTokens(i))
                        If i = 0 Then _writer.Write("Nothing, ")
                    Next i
                    _writer.WriteLine()
 
                    _writer.WriteLine("            catch e as ArgumentNullException")
                    _writer.WriteLine("            exceptionTest = true")
                    _writer.WriteLine("            End Try")
                    _writer.WriteLine("            Debug.Assert(exceptionTest)")
                    _writer.WriteLine()
 
                    _writer.WriteLine("            return node2")
                Else
                    _writer.Write("            return ")
                    callTokens.ForEach(AddressOf _writer.Write)
                    _writer.WriteLine()
 
                End If
 
            End If
 
        Else
 
            Dim children = GetAllFactoryChildrenOfStructure(nodeStructure)
 
            If nodeStructure.IsTrivia Then
                callTokens.Add("String.Empty")
                anePositions.Add(callTokens.Count - 1)
                first = False
            End If
 
            For Each child In children
                If Not first Then
                    callTokens.Add(", ")
                End If
 
                If child.IsOptional Then
                    ' Hack: remove when the factory methods have been fixed to not contain overloads.
                    If nodeStructure.Name = "MemberAccessExpressionSyntax" Then
                        If first Then
                            callTokens.Add(String.Format("CType(Nothing, {0}{1})", namespacePrefix, ChildFieldTypeRef(child)))
                        End If
                    Else
                        callTokens.Add("Nothing")
                    End If
 
                    ' TODO: remove
                    first = False
 
                    Continue For
                End If
 
                ' TODO: move up.
                first = False
 
                Dim childNodeKind As ParseNodeKind = If(Not child Is Nothing, TryCast(child.ChildKind, ParseNodeKind), Nothing)
 
                If TypeOf child.ChildKind Is List(Of ParseNodeKind) Then
                    childNodeKind = child.ChildKind(nodeKind.Name)
                End If
 
                If childNodeKind Is Nothing Then
                    childNodeKind = DirectCast(child.ChildKind, List(Of ParseNodeKind)).Item(0)
                End If
 
                If child.IsList AndAlso child.IsSeparated Then
                    Dim childKindStructure = child.ParseTree.NodeStructures(childNodeKind.StructureId)
                    childKindStructure = If(Not childKindStructure.ParentStructure Is Nothing, childKindStructure.ParentStructure, childKindStructure)
                    callTokens.Add("New " + ChildFactoryTypeRef(nodeStructure, child, isGreen, True) + "()")
                Else
                    Dim structureOfchild = child.ParseTree.NodeStructures(childNodeKind.StructureId)
                    If structureOfchild.Name = "PunctuationSyntax" OrElse structureOfchild.Name = "KeywordSyntax" Then
 
                        If isGreen Then
                            callTokens.Add("new InternalSyntax." + structureOfchild.Name + "(")
                            callTokens.Add("SyntaxKind." + childNodeKind.Name + ", String.Empty, Nothing, Nothing)")
                        Else
                            Dim token = "SyntaxFactory.Token(SyntaxKind." + childNodeKind.Name + ")"
                            If child.IsList Then
                                token = "SyntaxTokenList.Create(" & token & ")"
                            End If
                            callTokens.Add(token)
 
                            ' add none kind here
                            If Not TypeOf child.ChildKind Is List(Of ParseNodeKind) Then
                                If child.IsOptional Then
                                    KindNonePositions.Add(callTokens.Count - 1)
                                End If
                            Else
                                If Not child.IsList Then
                                    aePositions.Add(callTokens.Count - 1)
                                End If
                            End If
                        End If
                    Else
                        If isGreen Then
                            callTokens.Add("GenerateGreen" + FactoryName(childNodeKind) + "()")
                        Else
                            Dim result = "GenerateRed" + FactoryName(childNodeKind) + "()"
                            If structureOfchild.IsToken AndAlso child.IsList Then
                                result = "SyntaxTokenList.Create(" & result & ")"
                            ElseIf child.IsSeparated Then
                                result = String.Format("SyntaxFactory.SingletonSeparatedList(Of {0}({1})", BaseTypeReference(child), result)
                            ElseIf child.IsList Then
                                result = String.Format("SyntaxFactory.SingletonList(Of {0})({1})", BaseTypeReference(child), result)
                            End If
                            callTokens.Add(result)
                        End If
                    End If
 
                    If Not KindTypeStructure(child.ChildKind).IsToken AndAlso Not child.IsList Then
                        anePositions.Add(callTokens.Count - 1)
                    ElseIf KindTypeStructure(child.ChildKind).IsToken And Not child.IsList Then
                        aePositions.Add(callTokens.Count - 1)
                    End If
                End If
            Next
 
            callTokens.Add(")")
 
            ' TODO: remove extra conditions
            If isGreen OrElse nodeStructure.Name = "CaseBlockSyntax" OrElse nodeStructure.Name = "IfPartSyntax" OrElse nodeStructure.Name = "MultiLineIfBlockSyntax" Then
                _writer.Write("            return ")
                callTokens.ForEach(AddressOf _writer.Write)
                _writer.WriteLine()
            Else
 
                _writer.WriteLine("            Dim exceptionTest as boolean = false")
 
                For exceptionChecks = 0 To anePositions.Count - 1
 
                    _writer.WriteLine("            Try")
 
                    _writer.Write("            ")
                    For i = 0 To callTokens.Count - 1
                        If (i <> anePositions(exceptionChecks)) Then
                            _writer.Write(callTokens(i))
                        Else
                            _writer.Write("Nothing")
                        End If
                    Next
                    _writer.WriteLine()
 
                    _writer.WriteLine("            catch e as ArgumentNullException")
                    _writer.WriteLine("            exceptionTest = true")
                    _writer.WriteLine("            End Try")
                    _writer.WriteLine("            Debug.Assert(exceptionTest)")
                    _writer.WriteLine("            exceptionTest = false")
                    _writer.WriteLine()
                Next
 
                For exceptionChecks = 0 To aePositions.Count - 1
 
                    _writer.WriteLine("            Try")
                    _writer.Write("            ")
                    For i = 0 To callTokens.Count - 1
                        If (i <> aePositions(exceptionChecks)) Then
                            _writer.Write(callTokens(i))
                        Else
                            _writer.Write("SyntaxFactory.Token(SyntaxKind.ExternalSourceKeyword)") ' this syntaxtoken should not be legal anywhere in the tests
                        End If
                    Next
                    _writer.WriteLine()
 
                    _writer.WriteLine("            catch e as ArgumentException")
                    _writer.WriteLine("            exceptionTest = true")
                    _writer.WriteLine("            End Try")
                    _writer.WriteLine("            Debug.Assert(exceptionTest)")
                    _writer.WriteLine("            exceptionTest = false")
                    _writer.WriteLine()
                Next
 
                For exceptionChecks = 0 To KindNonePositions.Count - 1
 
                    _writer.Write("            ")
                    For i = 0 To callTokens.Count - 1
                        If (i <> KindNonePositions(exceptionChecks)) Then
                            _writer.Write(callTokens(i))
                        Else
                            _writer.Write("New SyntaxToken(Nothing, New InternalSyntax.KeywordSyntax(SyntaxKind.None, Nothing, Nothing, """", Nothing, Nothing), 0, 0)")
                        End If
                    Next
                    _writer.WriteLine()
                    _writer.WriteLine()
                Next
 
                _writer.Write("            return ")
                callTokens.ForEach(AddressOf _writer.Write)
                _writer.WriteLine()
            End If
 
        End If
 
        _writer.WriteLine("        End Function")
        _writer.WriteLine()
    End Sub
 
    Private Sub GenerateFactoryCallTests(isGreen As Boolean)
        For Each nodeStructure In _parseTree.NodeStructures.Values
            If Not nodeStructure.Abstract AndAlso Not nodeStructure.NoFactory Then
                If Not nodeStructure.Name = "KeywordSyntax" AndAlso Not nodeStructure.Name = "PunctuationSyntax" Then
                    GenerateFactoryCallTest(isGreen, nodeStructure)
                End If
            End If
        Next
    End Sub
 
    Private Sub GenerateFactoryCallTest(isGreen As Boolean, nodeStructure As ParseNodeStructure)
        For Each kind In nodeStructure.NodeKinds
            GenerateFactoryCallTest(isGreen, nodeStructure, kind)
        Next
    End Sub
 
    Private Sub GenerateFactoryCallTest(isGreen As Boolean, nodeStructure As ParseNodeStructure, nodeKind As ParseNodeKind)
 
        If nodeKind.Name.Contains(s_externalSourceDirectiveString) Then
            Return ' check for fix
        End If
 
        Dim funcNamePart = If(isGreen, "Green", "Red")
 
        _writer.WriteLine("        <Fact>")
        _writer.Write("        Public Sub ")
 
        _writer.Write("Test{0}{1}", funcNamePart, FactoryName(nodeKind))
        _writer.WriteLine("()")
 
        _writer.WriteLine("            dim objectUnderTest = Generate{0}{1}()", funcNamePart, FactoryName(nodeKind))
 
        'Dim children = GetAllChildrenOfStructure(nodeStructure)
 
        If isGreen Then
            _writer.WriteLine("            AttachAndCheckDiagnostics(objectUnderTest)")
        Else
            Dim withStat As String = Nothing
            For Each child In GetAllChildrenOfStructure(nodeStructure)
                If Not child.IsOptional Then
                    _writer.WriteLine("            Assert.NotNull(objectUnderTest.{0})", LowerFirstCharacter(child.Name))
                End If
                withStat += String.Format(".With{0}(objectUnderTest.{0})", child.Name)
            Next
            If (withStat IsNot Nothing) Then
                _writer.WriteLine("            Dim withObj = objectUnderTest{0}", withStat)
                _writer.WriteLine("            Assert.Equal(withobj, objectUnderTest)")
            End If
        End If
 
        _writer.WriteLine("        End Sub")
        _writer.WriteLine()
    End Sub
 
    Private Sub GenerateRewriterTests(isGreen As Boolean)
        For Each nodeStructure In _parseTree.NodeStructures.Values
            If Not nodeStructure.Abstract AndAlso Not nodeStructure.NoFactory AndAlso Not nodeStructure.IsToken AndAlso Not nodeStructure.IsTrivia Then
                GenerateRewriterTest(isGreen, nodeStructure)
            End If
        Next
    End Sub
 
    Private Sub GenerateRewriterTest(isGreen As Boolean, nodeStructure As ParseNodeStructure)
        For Each kind In nodeStructure.NodeKinds
            GenerateRewriterTest(isGreen, nodeStructure, kind)
        Next
    End Sub
 
    Private Sub GenerateRewriterTest(isGreen As Boolean, nodeStructure As ParseNodeStructure, nodeKind As ParseNodeKind)
 
        If nodeKind.Name.Contains(s_externalSourceDirectiveString) Then
            Return ' check for fix
        End If
 
        Dim funcNamePart = If(isGreen, "Green", "Red")
 
        _writer.WriteLine("        <Fact>")
        _writer.Write("        Public Sub ")
 
        _writer.Write("Test{0}{1}Rewriter", funcNamePart, FactoryName(nodeKind))
        _writer.WriteLine("()")
 
        _writer.WriteLine("            dim oldNode = Generate{0}{1}()", funcNamePart, FactoryName(nodeKind))
        _writer.WriteLine("            Dim rewriter = New {0}IdentityRewriter()", funcNamePart)
        _writer.WriteLine("            Dim newNode = rewriter.Visit(oldNode)")
        _writer.WriteLine("            Assert.Equal(oldNode, newNode)")
        _writer.WriteLine("        End Sub")
        _writer.WriteLine()
 
    End Sub
 
    Private Sub GenerateVisitorTests(isGreen As Boolean)
        For Each nodeStructure In _parseTree.NodeStructures.Values
            If Not nodeStructure.Abstract AndAlso Not nodeStructure.NoFactory AndAlso Not nodeStructure.IsToken AndAlso Not nodeStructure.IsTrivia Then
                GenerateVisitorTest(isGreen, nodeStructure)
            End If
        Next
    End Sub
 
    Private Sub GenerateVisitorTest(isGreen As Boolean, nodeStructure As ParseNodeStructure)
        For Each kind In nodeStructure.NodeKinds
            GenerateVisitorTest(isGreen, nodeStructure, kind)
        Next
    End Sub
 
    Private Sub GenerateVisitorTest(isGreen As Boolean, nodeStructure As ParseNodeStructure, nodeKind As ParseNodeKind)
 
        If nodeKind.Name.Contains(s_externalSourceDirectiveString) Then
            Return ' check for fix
        End If
 
        Dim funcNamePart = If(isGreen, "Green", "Red")
 
        _writer.WriteLine("        <Fact>")
        _writer.Write("        Public Sub ")
 
        _writer.Write("Test" + funcNamePart + FactoryName(nodeKind) + "Visitor")
        _writer.WriteLine("()")
 
        _writer.WriteLine("            Dim oldNode = Generate" + funcNamePart + FactoryName(nodeKind) + "()")
        _writer.WriteLine("            Dim visitor = New " + funcNamePart + "NodeVisitor()")
        _writer.WriteLine("            visitor.Visit(oldNode)")
        _writer.WriteLine("        End Sub")
        _writer.WriteLine()
    End Sub
 
    Public Function GetInitValueForType(fieldType As String) As String
        Select Case fieldType
            Case "Integer"
                Return "23"
            Case "String"
                Return """Bar"""
            Case "Char"
                Return """E""C"
            Case "DateTime"
                Return "New DateTime(2008,11,04)"
            Case "System.Decimal"
                Return "42"
            Case "TypeCharacter"
                Return "TypeCharacter.DecimalLiteral"
            Case "SyntaxKind"
                Return "SyntaxKind.IdentifierName"
            Case Else
                Return "Unknown Type"
        End Select
    End Function
 
    Public Sub AddHandwrittenFactoryCall(baseType As String)
 
        Select Case baseType
            Case "IdentifierTokenSyntax"
                _writer.Write("new InternalSyntax.SimpleIdentifierSyntax(SyntaxKind.IdentifierToken, Nothing, Nothing, ""text"",")
                _writer.Write("InternalSyntax.SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "" ""), ")
                _writer.Write("InternalSyntax.SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "" ""))")
 
            Case "IntegerLiteralTokenSyntax"
                _writer.Write("new InternalSyntax.IntegerLiteralToken(""42"", LiteralBase.Decimal, TypeCharacter.None, 42,")
                _writer.Write("InternalSyntax.SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "" ""), ")
                _writer.Write("InternalSyntax.SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "" ""))")
 
        End Select
    End Sub
 
End Class