public static ClassDeclarationSyntax Convert(ClassDeclarationSyntax node, RequiredUsings requires)
        {
            var tearDownMethod = node.FindMethodWithAttribute("TearDown").SingleOrDefault();

            // this class has no teardown method
            if (tearDownMethod == null)
            {
                return(node);
            }

            requires.System = true;

            var disposeMethod = MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), Identifier("Dispose"))
                                .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
                                .NormalizeWhitespace()
                                .WithTriviaFrom(tearDownMethod)
                                .WithBody(tearDownMethod.Body);

            return(node
                   .ReplaceNode(tearDownMethod, disposeMethod)
                   .WithIdentifier(
                       node.Identifier.WithTrailingTrivia(Whitespace(" "))
                       )
                   .WithBaseList(node.BaseList?.NormalizeWhitespace())
                   .AddBaseListTypes(
                       SimpleBaseType(IdentifierName("IDisposable"))
                       .WithLeadingTrivia(Whitespace(" "))
                       .WithTrailingTrivia(Whitespace(Environment.NewLine))
                       ));
        }
Ejemplo n.º 2
0
        public override void ExitMapType(GolangParser.MapTypeContext context)
        {
            string type = context.GetText();

            Types.TryGetValue(context.type(), out TypeInfo keyTypeInfo);

            if (keyTypeInfo == null)
            {
                return; // throw new InvalidOperationException("Map key type undefined.");
            }
            Types.TryGetValue(context.elementType().type(), out TypeInfo elementTypeInfo);

            if (elementTypeInfo == null)
            {
                return; // throw new InvalidOperationException("Map element type undefined.");
            }
            RequiredUsings.Add("System.Collections.Generic");

            Types[context.Parent.Parent] = new MapTypeInfo
            {
                Name            = type,
                TypeName        = $"Dictionary<{keyTypeInfo.TypeName}, {elementTypeInfo.TypeName}>",
                FullTypeName    = $"System.Collections.Generic.Dictionary<{keyTypeInfo.FullTypeName}, {elementTypeInfo.FullTypeName}>",
                ElementTypeInfo = elementTypeInfo,
                KeyTypeInfo     = keyTypeInfo,
                TypeClass       = TypeClass.Map
            };
        }
Ejemplo n.º 3
0
        public override void EnterPackageClause(GoParser.PackageClauseContext context)
        {
            // Go package clause is the first keyword encountered - cache details that
            // will be written out after imports. C# import statements (i.e., usings)
            // typically occur before namespace and class definitions
            string[] paths            = PackageImport.Split('/').Select(SanitizedIdentifier).ToArray();
            string   packageNamespace = $"{RootNamespace}.{string.Join(".", paths)}";

            PackageUsing     = $"{Package} = {packageNamespace}{ClassSuffix}";
            PackageNamespace = packageNamespace.Substring(0, packageNamespace.LastIndexOf('.'));

            // Track file name associated with package
            AddFileToPackage(Package, TargetFileName, PackageNamespace);

            // Define namespaces
            List <string> packageNamespaces = new List <string> {
                RootNamespace
            };

            if (paths.Length > 1)
            {
                packageNamespaces.AddRange(paths);
                packageNamespaces.RemoveAt(packageNamespaces.Count - 1);
            }

            PackageNamespaces = packageNamespaces.ToArray();

            string headerLevelComments = CheckForCommentsLeft(context);

            m_packageLevelComments = CheckForCommentsRight(context);

            if (!string.IsNullOrWhiteSpace(headerLevelComments))
            {
                m_targetFile.Append(headerLevelComments);

                if (!EndsWithLineFeed(headerLevelComments))
                {
                    m_targetFile.AppendLine();
                }
            }

            m_targetFile.AppendLine($"// package {Package} -- go2cs converted at {DateTime.UtcNow:yyyy MMMM dd HH:mm:ss} UTC");

            if (!PackageImport.Equals("main"))
            {
                m_targetFile.AppendLine($"// import \"{PackageImport}\" ==> using {PackageUsing}");
            }

            m_targetFile.AppendLine($"// Original source: {SourceFileName}");


            // Add commonly required using statements
            RequiredUsings.Add("static go.builtin");
        }
Ejemplo n.º 4
0
        public override void EnterImportDecl(GoParser.ImportDeclContext context)
        {
            if (!string.IsNullOrWhiteSpace(m_packageLevelComments))
            {
                m_targetFile.Append(m_packageLevelComments.TrimStart());

                if (!EndsWithLineFeed(m_packageLevelComments))
                {
                    m_targetFile.AppendLine();
                }
            }

            m_usingStatements.UnionWith(RequiredUsings.Select(usingType => $"using {usingType};"));
        }
Ejemplo n.º 5
0
        public static CompilationUnitSyntax AddUsings(CompilationUnitSyntax node, RequiredUsings requiredUsings, Options options)
        {
            // Remember leading trivia (e.g. license header comments) so we can restore
            // it later. For some reason, manipulating the usings can remove it.
            var comment = node.GetLeadingTrivia();
            var treeWithTriviaTrimmed = options.ConvertAssert
                ? node.RemoveNUnitUsing().WithoutLeadingTrivia()
                : node.WithoutLeadingTrivia();

            // add any usings that were required when visiting the tree
            var additionalUsings = GenerateAdditionalUsings(node.Usings, requiredUsings, options).ToArray();
            var treeWithUsings   = AddUsingsToCompilationUnit(treeWithTriviaTrimmed, additionalUsings);

            // restore the leading trivia to the new syntax tree.
            var treeWithTriviaRestored = treeWithUsings.WithLeadingTrivia(comment);

            return(treeWithTriviaRestored);
        }
Ejemplo n.º 6
0
        public override void ExitFunctionType(GoParser.FunctionTypeContext context)
        {
            Signatures.TryGetValue(context.signature(), out Signature signature);

            string typeList = signature.GenerateParameterTypeList();
            string resultSignature = signature.GenerateResultSignature();
            string typeName, fullTypeName;

            RequiredUsings.Add("System");

            if (resultSignature == "void")
            {
                if (string.IsNullOrEmpty(typeList))
                {
                    typeName     = "Action";
                    fullTypeName = "System.Action";
                }
                else
                {
                    typeName     = $"Action<{typeList}>";
                    fullTypeName = $"System.Action<{typeList}>";
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(typeList))
                {
                    typeList = $"{typeList}, ";
                }

                typeName     = $"Func<{typeList}{resultSignature}>";
                fullTypeName = $"System.Func<{typeList}{resultSignature}>";
            }

            Types[context.Parent.Parent] = new TypeInfo
            {
                Name         = context.GetText(),
                TypeName     = typeName,
                FullTypeName = fullTypeName,
                TypeClass    = TypeClass.Function
            };
        }
Ejemplo n.º 7
0
        public static ClassDeclarationSyntax Convert(ClassDeclarationSyntax node, RequiredUsings requires)
        {
            // get all the TestCaseSource attributes
            var testCaseSources = FindTestCaseSourceAttributes(node);

            if (!testCaseSources.Any())
            {
                return(node);
            }

            requires.SystemCollectionsGeneric = true;

            var memberNames = GetMemberNamesFromArguments(testCaseSources);

            var publicStaticFields = GenerateReplacementMembers(node, memberNames);

            // update the syntax tree with the new members
            var rewritten = node.ReplaceNodes(
                publicStaticFields.Keys,
                (oldNode, _) => publicStaticFields[oldNode].WithTriviaFrom(oldNode)
                );

            return(rewritten);
        }
Ejemplo n.º 8
0
        public static AttributeListSyntax Convert(AttributeListSyntax node, RequiredUsings requires)
        {
            var testFixture = node.Attributes
                              .SingleOrDefault(attribute => attribute.IsTestFixtureAttribute());

            // no test fixture, this isn't an NUnit class
            if (testFixture == null)
            {
                return(node);
            }

            requires.XUnit = true;

            // only one attribute in the list (e.g. [TestFixture] ), remove the entire list.
            if (node.Attributes.Count == 1)
            {
                return(NullAttributeList(node));
            }

            // multiple attributes in a list (e.g. [TestFixture, DataContract] ), remove only the TestFixture attribute.
            var newList = node.RemoveNode(testFixture, SyntaxRemoveOptions.KeepExteriorTrivia);

            return(newList);
        }
Ejemplo n.º 9
0
        public override void ExitFunctionLit(GoParser.FunctionLitContext context)
        {
            // functionLit
            //     : 'func' function

            string parametersSignature = "()";

            if (Signatures.TryGetValue(context?.signature(), out Signature signature))
            {
                parametersSignature = signature.GenerateParameterNameList();

                if (signature.Parameters.Length != 1)
                {
                    parametersSignature = $"({parametersSignature})";
                }
            }
            else
            {
                AddWarning(context, $"Failed to find signature for function literal inside \"{m_currentFunctionName}\" function");
            }

            // Replace marker for function literal
            m_targetFile.Replace(FunctionLiteralParametersMarker, parametersSignature);

            // operand
            //     : literal
            //     | operandName
            //     | methodExpr
            //     | '(' expression ')'

            // literal
            //     : basicLit
            //     | compositeLit
            //     | functionLit

            if (!(context?.Parent.Parent is GoParser.OperandContext operandContext))
            {
                AddWarning(context, $"Could not derive parent operand context from function literal inside \"{m_currentFunctionName}\" function: \"{context.GetText()}\"");
                PopBlock();
                return;
            }

            string lambdaExpression = PopBlock(false);

            // Simplify lambda expressions that consist of a single return statement
            if (m_firstStatementIsReturn)
            {
                int index = lambdaExpression.IndexOf("=>", StringComparison.Ordinal);

                if (index > -1)
                {
                    string startBlock = $"{{{Environment.NewLine}";

                    index = lambdaExpression.IndexOf(startBlock, index, StringComparison.Ordinal);

                    if (index > -1)
                    {
                        string parameters = lambdaExpression.Substring(0, index).Trim();

                        lambdaExpression = lambdaExpression.Substring(index + startBlock.Length).Trim();

                        if (lambdaExpression.StartsWith("return ", StringComparison.Ordinal))
                        {
                            lambdaExpression = lambdaExpression.Substring(7).Trim();
                        }

                        if (lambdaExpression.EndsWith("}", StringComparison.Ordinal))
                        {
                            lambdaExpression = lambdaExpression.Substring(0, lambdaExpression.Length - 1).Trim();
                        }

                        if (lambdaExpression.EndsWith(";", StringComparison.Ordinal))
                        {
                            lambdaExpression = lambdaExpression.Substring(0, lambdaExpression.Length - 1).Trim();
                        }

                        lambdaExpression = $"{parameters} {lambdaExpression}";
                    }
                }
            }
            RequiredUsings.Add("System");

            string typeList = signature.GenerateParameterTypeList();
            string resultSignature = signature.GenerateResultSignature();
            string typeName, fullTypeName;

            if (resultSignature == "void")
            {
                if (string.IsNullOrEmpty(typeList))
                {
                    typeName     = "Action";
                    fullTypeName = "System.Action";
                }
                else
                {
                    typeName     = $"Action<{typeList}>";
                    fullTypeName = $"System.Action<{typeList}>";
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(typeList))
                {
                    typeList = $"{typeList}, ";
                }

                typeName     = $"Func<{typeList}{resultSignature}>";
                fullTypeName = $"System.Func<{typeList}{resultSignature}>";
            }

            // Update expression operand (managed in ScannerBase_Expression.cs)
            Operands[operandContext] = new ExpressionInfo
            {
                Text = lambdaExpression,
                Type = new TypeInfo
                {
                    Name         = parametersSignature,
                    TypeName     = typeName,
                    FullTypeName = fullTypeName,
                    TypeClass    = TypeClass.Function
                }
            };
        }
Ejemplo n.º 10
0
        public override void ExitTypeSpec(GolangParser.TypeSpecContext context)
        {
            // typeSpec
            //     : IDENTIFIER type

            if (m_firstTypeSpec)
            {
                m_firstTypeSpec = false;

                string comments = CheckForCommentsLeft(context, preserveLineFeeds: m_inFunction);

                if (!string.IsNullOrEmpty(comments))
                {
                    m_targetFile.Append(FixForwardSpacing(comments));
                }
            }

            string originalIdentifier = context.IDENTIFIER().GetText();
            string scope      = char.IsUpper(originalIdentifier[0]) ? "public" : "private";
            string target     = Path.GetFileNameWithoutExtension(TargetFileName);
            string identifier = SanitizedIdentifier(originalIdentifier);

            // TODO: Sub-function strategy, declare directly prior to function using PushBlock / PopBlock operations and a new replacement marker
            if (m_inFunction)
            {
                AddWarning(context, $"Type specification made from within function \"{m_currentFunctionName}\" - this is will not compile in C#");
            }

            if (Metadata.Interfaces.TryGetValue(originalIdentifier, out InterfaceInfo interfaceInfo))
            {
                // Handle interface type declaration
                string ancillaryInterfaceFileName = Path.Combine(TargetFilePath, $"{target}_{identifier}Interface.cs");

                FunctionSignature[]      localFunctions = interfaceInfo.GetLocalMethods().ToArray();
                List <FunctionSignature> allFunctions   = localFunctions.ToList();

                RecurseInheritedInterfaces(context, originalIdentifier, interfaceInfo, allFunctions);

                using (StreamWriter writer = File.CreateText(ancillaryInterfaceFileName))
                {
                    writer.Write(new InterfaceTypeTemplate
                    {
                        NamespacePrefix = PackageNamespace,
                        NamespaceHeader = m_namespaceHeader,
                        NamespaceFooter = m_namespaceFooter,
                        PackageName     = Package,
                        InterfaceName   = identifier,
                        Scope           = scope,
                        Interface       = interfaceInfo,
                        Functions       = allFunctions,
                        UsingStatements = m_usingStatements
                    }
                                 .TransformText());
                }

                // Track file name associated with package
                AddFileToPackage(Package, ancillaryInterfaceFileName, PackageNamespace);

                string inheritedInterfaces = interfaceInfo.GenerateInheritedInterfaceList();

                if (inheritedInterfaces.Length > 0)
                {
                    inheritedInterfaces = $" : {inheritedInterfaces}";
                }

                m_targetFile.AppendLine($"{Spacing()}{scope} partial interface {identifier}{inheritedInterfaces}");
                m_targetFile.AppendLine($"{Spacing()}{{");

                foreach (FunctionSignature function in localFunctions)
                {
                    m_targetFile.Append($"{Spacing(1)}{function.Signature.GenerateResultSignature()} {SanitizedIdentifier(function.Name)}({function.Signature.GenerateParametersSignature(false)});{(string.IsNullOrEmpty(function.Comments) ? Environment.NewLine : function.Comments)}");
                }

                m_targetFile.Append($"{Spacing()}}}{CheckForBodyCommentsRight(context)}");
            }
            else if (Metadata.Structs.TryGetValue(originalIdentifier, out StructInfo structInfo))
            {
                // Handle struct type declaration
                string ancillaryStructFileName = Path.Combine(TargetFilePath, $"{target}_{identifier}Struct.cs");

                Dictionary <string, List <FunctionSignature> > promotedFunctions = new Dictionary <string, List <FunctionSignature> >(StringComparer.Ordinal);
                HashSet <string> inheritedTypeNames = new HashSet <string>(StringComparer.Ordinal);

                RecurseInheritedInterfaces(context, originalIdentifier, structInfo, promotedFunctions, inheritedTypeNames);

                Dictionary <string, List <FieldInfo> > promotedFields = new Dictionary <string, List <FieldInfo> >(StringComparer.Ordinal);
                HashSet <string> promotedStructs = new HashSet <string>(StringComparer.Ordinal);

                SearchPromotedStructFields(context, originalIdentifier, structInfo, inheritedTypeNames, promotedFields, promotedStructs);

                // Mark private structures as internal so that conversion function can be accessed from builtin functions
                if (scope.Equals("private"))
                {
                    scope = "internal";
                }

                using (StreamWriter writer = File.CreateText(ancillaryStructFileName))
                {
                    writer.Write(new StructTypeTemplate
                    {
                        NamespacePrefix   = PackageNamespace,
                        NamespaceHeader   = m_namespaceHeader,
                        NamespaceFooter   = m_namespaceFooter,
                        PackageName       = Package,
                        StructName        = identifier,
                        Scope             = scope,
                        StructFields      = structInfo.Fields,
                        PromotedStructs   = promotedStructs,
                        PromotedFunctions = promotedFunctions,
                        PromotedFields    = promotedFields,
                        UsingStatements   = m_usingStatements
                    }
                                 .TransformText());
                }

                // Track file name associated with package
                AddFileToPackage(Package, ancillaryStructFileName, PackageNamespace);

                List <FieldInfo> fields = new List <FieldInfo>();

                foreach (FieldInfo field in structInfo.Fields)
                {
                    if (field.IsPromoted && !inheritedTypeNames.Contains(field.Name))
                    {
                        FieldInfo promotedStruct = field.Clone();

                        promotedStruct.Type.TypeName = $"ref {promotedStruct.Type.Name}";
                        promotedStruct.Name          = $"{promotedStruct.Name} => ref {promotedStruct.Name}_{(promotedStruct.Type.IsPointer ? "ptr" : "val")}";

                        fields.Add(promotedStruct);
                    }
                    else
                    {
                        fields.Add(field);
                    }
                }

                m_targetFile.AppendLine($"{Spacing()}{scope} partial struct {identifier}{GetInheritedTypeList(inheritedTypeNames)}");
                m_targetFile.AppendLine($"{Spacing()}{{");

                foreach (FieldInfo field in fields)
                {
                    StringBuilder fieldDecl = new StringBuilder();

                    if (!string.IsNullOrWhiteSpace(field.Description))
                    {
                        string description = field.Description.Trim();

                        if (description.Length > 2)
                        {
                            RequiredUsings.Add("System.ComponentModel");
                            fieldDecl.AppendLine($"{Spacing(1)}[Description({description})]");
                        }
                    }

                    fieldDecl.Append($"{Spacing(1)}public {field.Type.TypeName} {field.Name};{(string.IsNullOrEmpty(field.Comments) ? Environment.NewLine : field.Comments)}");
                    m_targetFile.Append(fieldDecl);
                }

                m_targetFile.Append($"{Spacing()}}}{CheckForBodyCommentsRight(context)}");
            }
            else if (Types.TryGetValue(context.type(), out TypeInfo typeInfo))
            {
                if (typeInfo.TypeClass == TypeClass.Function)
                {
                    RequiredUsings.Decrement("System");

                    // Handle delegate type declaration
                    string signature = typeInfo.TypeName;

                    if (signature.Equals("Action", StringComparison.Ordinal))
                    {
                        m_targetFile.Append($"{Spacing()}public delegate void {identifier}();");
                        m_targetFile.Append(CheckForBodyCommentsRight(context));
                    }
                    else if (signature.StartsWith("Action<", StringComparison.Ordinal))
                    {
                        signature = RemoveSurrounding(signature.Substring(6), "<", ">");
                        m_targetFile.Append($"{Spacing()}public delegate void {identifier}({signature});");
                        m_targetFile.Append(CheckForBodyCommentsRight(context));
                    }
                    else if (signature.StartsWith("Func<", StringComparison.Ordinal))
                    {
                        signature = RemoveSurrounding(signature.Substring(4), "<", ">");
                        string[] parts = signature.Split(',');

                        if (parts.Length > 0)
                        {
                            string result = parts[parts.Length - 1];
                            signature = string.Join(", ", parts.Take(parts.Length - 1));

                            m_targetFile.Append($"{Spacing()}public delegate {result} {identifier}({signature});");
                            m_targetFile.Append(CheckForBodyCommentsRight(context));
                        }
                        else
                        {
                            AddWarning(context, $"Could not determine function based delegate signature from \"{signature}\"");
                        }
                    }
                    else
                    {
                        AddWarning(context, $"Could not determine delegate signature from \"{signature}\"");
                    }
                }
                else
                {
                    // Handle named type declaration, e.g., "type MyFloat float64"
                    string ancillaryInheritedTypeFileName = Path.Combine(TargetFilePath, $"{target}_{identifier}StructOf({RemoveInvalidCharacters(typeInfo.TypeName)}).cs");

                    // TODO: The following works OK for a primitive type re-definition, but new templates will be needed for other inherited types, e.g., Map / Pointer / Array etc.
                    using (StreamWriter writer = File.CreateText(ancillaryInheritedTypeFileName))
                    {
                        writer.Write(new InheritedTypeTemplate
                        {
                            NamespacePrefix = PackageNamespace,
                            NamespaceHeader = m_namespaceHeader,
                            NamespaceFooter = m_namespaceFooter,
                            PackageName     = Package,
                            StructName      = identifier,
                            Scope           = scope,
                            TypeName        = typeInfo.TypeName
                        }
                                     .TransformText());
                    }

                    // Track file name associated with package
                    AddFileToPackage(Package, ancillaryInheritedTypeFileName, PackageNamespace);

                    m_targetFile.AppendLine($"{Spacing()}{scope} partial struct {identifier} // : {typeInfo.TypeName}");
                    m_targetFile.AppendLine($"{Spacing()}{{");
                    m_targetFile.Append($"{Spacing()}}}{CheckForBodyCommentsRight(context)}");
                }
            }
        }
Ejemplo n.º 11
0
        private static IEnumerable <UsingDirectiveSyntax> GenerateAdditionalUsings(SyntaxList <UsingDirectiveSyntax> existing, RequiredUsings requires, Options options)
        {
            if (requires.System && !HasUsing("System"))
            {
                yield return(CreateUsing("System"));
            }
            if (requires.SystemCollectionsGeneric && !HasUsing("System.Collections.Generic"))
            {
                yield return(CreateUsing("System.Collections.Generic"));
            }
            if (requires.XUnit && options.ConvertAssert && !HasUsing("Xunit"))
            {
                yield return(CreateUsing("Xunit"));
            }

            bool HasUsing(string identifier) =>
            existing.Any(u => u.Name.ToString() == identifier);

            UsingDirectiveSyntax CreateUsing(string identifier) =>
            UsingDirective(IdentifierName(identifier))
            .NormalizeWhitespace()
            .WithTrailingTrivia(Whitespace(Environment.NewLine));
        }
Ejemplo n.º 12
0
        public override void ExitTypeSpec(GoParser.TypeSpecContext context)
        {
            // typeSpec
            //     : IDENTIFIER type

            string originalIdentifier = context.IDENTIFIER().GetText();
            string scope      = char.IsUpper(originalIdentifier[0]) ? "public" : "private";
            string target     = Path.GetFileNameWithoutExtension(TargetFileName);
            string identifier = SanitizedIdentifier(originalIdentifier);

            // TODO: Sub-function strategy, declare directly prior to function using PushBlock / PopBlock operations and a new replacement marker
            if (InFunction)
            {
                AddWarning(context, $"Type specification made from within function \"{CurrentFunctionName}\" - this is will not compile in C#");
            }

            if (m_typeIdentifierCount == 0 && m_typeMultipleDeclaration)
            {
                m_targetFile.Append(RemoveFirstLineFeed(CheckForCommentsLeft(context)));
            }

            if (Metadata.Interfaces.TryGetValue(originalIdentifier, out InterfaceInfo interfaceInfo))
            {
                // Handle interface type declaration
                string ancillaryInterfaceFileName = Path.Combine(TargetFilePath, $"{GetValidPathName($"{target}_{identifier}Interface")}.cs");

                FunctionSignature[]      localFunctions = interfaceInfo.GetLocalMethods().ToArray();
                List <FunctionSignature> allFunctions   = localFunctions.ToList();

                RecurseInheritedInterfaces(context, originalIdentifier, interfaceInfo, allFunctions);

                using (StreamWriter writer = File.CreateText(ancillaryInterfaceFileName))
                {
                    writer.Write(new InterfaceTypeTemplate
                    {
                        NamespacePrefix = PackageNamespace,
                        NamespaceHeader = m_namespaceHeader,
                        NamespaceFooter = m_namespaceFooter,
                        PackageName     = Package,
                        InterfaceName   = identifier,
                        Scope           = scope,
                        Interface       = interfaceInfo,
                        Functions       = allFunctions,
                        UsingStatements = m_usingStatements
                    }
                                 .TransformText());
                }

                // Track file name associated with package
                AddFileToPackage(Package, ancillaryInterfaceFileName, PackageNamespace);

                string inheritedInterfaces = interfaceInfo.GenerateInheritedInterfaceList();

                if (inheritedInterfaces.Length > 0)
                {
                    inheritedInterfaces = $" : {inheritedInterfaces}";
                }

                m_targetFile.AppendLine($"{Spacing()}{scope} partial interface {identifier}{inheritedInterfaces}");
                m_targetFile.AppendLine($"{Spacing()}{{");

                foreach (FunctionSignature function in localFunctions)
                {
                    m_targetFile.Append($"{Spacing(1)}{function.Signature.GenerateResultSignature()} {SanitizedIdentifier(function.Name)}({function.Signature.GenerateParametersSignature()});{(string.IsNullOrWhiteSpace(function.Comments) ? Environment.NewLine : $" {function.Comments.TrimStart()}")}");
                }

                m_targetFile.Append($"{Spacing()}}}{CheckForCommentsRight(context)}");
            }
            else if (Metadata.Structs.TryGetValue(originalIdentifier, out StructInfo structInfo))
            {
                // Handle struct type declaration
                string ancillaryStructFileName = Path.Combine(TargetFilePath, $"{GetValidPathName($"{target}_{identifier}Struct")}.cs");

                Dictionary <string, List <FunctionSignature> > promotedFunctions = new Dictionary <string, List <FunctionSignature> >(StringComparer.Ordinal);
                HashSet <string> inheritedTypeNames = new HashSet <string>(StringComparer.Ordinal);

                RecurseInheritedInterfaces(context, originalIdentifier, structInfo, promotedFunctions, inheritedTypeNames);

                Dictionary <string, List <FieldInfo> > promotedFields = new Dictionary <string, List <FieldInfo> >(StringComparer.Ordinal);
                HashSet <string> promotedStructs = new HashSet <string>(StringComparer.Ordinal);

                SearchPromotedStructFields(context, originalIdentifier, structInfo, inheritedTypeNames, promotedFields, promotedStructs);

                using (StreamWriter writer = File.CreateText(ancillaryStructFileName))
                {
                    writer.Write(new StructTypeTemplate
                    {
                        NamespacePrefix   = PackageNamespace,
                        NamespaceHeader   = m_namespaceHeader,
                        NamespaceFooter   = m_namespaceFooter,
                        PackageName       = Package,
                        StructName        = identifier,
                        Scope             = scope,
                        StructFields      = structInfo.Fields,
                        PromotedStructs   = promotedStructs,
                        PromotedFunctions = promotedFunctions,
                        PromotedFields    = promotedFields,
                        UsingStatements   = m_usingStatements
                    }
                                 .TransformText());
                }

                // Track file name associated with package
                AddFileToPackage(Package, ancillaryStructFileName, PackageNamespace);

                List <FieldInfo> fields = new List <FieldInfo>();

                foreach (FieldInfo field in structInfo.Fields)
                {
                    if (field.IsPromoted && !inheritedTypeNames.Contains(field.Name))
                    {
                        FieldInfo promotedStruct = field.Clone();

                        promotedStruct.Type.TypeName = $"ref {promotedStruct.Type.Name}";
                        promotedStruct.Name          = $"{promotedStruct.Name} => ref {promotedStruct.Name}_{(promotedStruct.Type is PointerTypeInfo ? "ptr" : "val")}";

                        fields.Add(promotedStruct);
                    }
                    else
                    {
                        fields.Add(field);
                    }
                }

                m_targetFile.AppendLine($"{Spacing()}{scope} partial struct {identifier}{GetInheritedTypeList(inheritedTypeNames)}");
                m_targetFile.AppendLine($"{Spacing()}{{");

                foreach (FieldInfo field in fields)
                {
                    StringBuilder fieldDecl = new StringBuilder();

                    if (!string.IsNullOrWhiteSpace(field.Description))
                    {
                        string description = field.Description.Trim();

                        if (description.Length > 2)
                        {
                            RequiredUsings.Add("System.ComponentModel");
                            fieldDecl.AppendLine($"{Spacing(1)}[Description({description})]");
                        }
                    }

                    fieldDecl.Append($"{Spacing(1)}public {field.Type.TypeName} {field.Name};{(string.IsNullOrWhiteSpace(field.Comments) ? Environment.NewLine : $" {field.Comments.TrimStart()}")}");
                    m_targetFile.Append(fieldDecl);
                }

                m_targetFile.Append($"{Spacing()}}}{CheckForCommentsRight(context)}");
            }
            else if (Types.TryGetValue(context.type_(), out TypeInfo typeInfo))
            {
                if (typeInfo.TypeClass == TypeClass.Function)
                {
                    RequiredUsings.Decrement("System");

                    // Handle delegate type declaration
                    string signature = typeInfo.TypeName;

                    if (signature.Equals("Action", StringComparison.Ordinal))
                    {
                        m_targetFile.Append($"{Spacing()}public delegate void {identifier}();");
                        m_targetFile.Append(CheckForCommentsRight(context));
                    }
                    else if (signature.StartsWith("Action<", StringComparison.Ordinal))
                    {
                        signature = RemoveSurrounding(signature.Substring(6), "<", ">");
                        m_targetFile.Append($"{Spacing()}public delegate void {identifier}({signature});");
                        m_targetFile.Append(CheckForCommentsRight(context));
                    }
                    else if (signature.StartsWith("Func<", StringComparison.Ordinal))
                    {
                        signature = RemoveSurrounding(signature.Substring(4), "<", ">");
                        string[] parts = signature.Split(',');

                        if (parts.Length > 0)
                        {
                            string result = parts[^ 1];
 public NUnitToXUnitVisitor(Options options)
 {
     this.options  = options;
     this.requires = AddRequiredUsings.GetDefaultState();
 }