File: ContainingSyntaxContext.cs
Web Access
Project: Microsoft.Interop.SourceGeneration.csproj (Microsoft.Interop.SourceGeneration)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Immutable;
using System.Linq;
using SourceGenerators;

namespace Microsoft.Interop
{
    public sealed record ContainingSyntaxContext(ImmutableArray<DeclarationHeader> ContainingSyntax, string? ContainingNamespace)
    {
        public ContainingSyntaxContext AddContainingSyntax(DeclarationHeader nestedType)
        {
            return this with { ContainingSyntax = ContainingSyntax.Insert(0, nestedType) };
        }

        public bool Equals(ContainingSyntaxContext? other)
        {
            return other is not null
                && ContainingSyntax.SequenceEqual(other.ContainingSyntax)
                && ContainingNamespace == other.ContainingNamespace;
        }

        public override int GetHashCode()
        {
            int hash = ContainingNamespace?.GetHashCode() ?? 0;
            foreach (DeclarationHeader containingSyntax in ContainingSyntax)
            {
                hash = HashCode.Combine(hash, containingSyntax.GetHashCode());
            }
            return hash;
        }

        /// <summary>Wraps a member in its containing types and namespace.</summary>
        public string WrapMemberInContainingSyntax(string member)
        {
            var writer = new IndentedTextWriter();
            WriteTo(writer, member, static (writer, member) => WriteMember(writer, member), addUnsafe: false);
            return writer.ToString();
        }

        /// <summary>Wraps members, adding unsafe to containing types only under the legacy memory safety rules.</summary>
        public string WrapMembersInContainingSyntaxWithUnsafeModifier(bool useUpdatedMemorySafetyRules, params string[] members)
        {
            var writer = new IndentedTextWriter();
            WriteToWithUnsafeModifier(useUpdatedMemorySafetyRules, writer, members, static (writer, members) =>
            {
                foreach (string member in members)
                {
                    WriteMember(writer, member);
                }
            });
            return writer.ToString();
        }

        /// <summary>Writes containing declarations with their original modifiers.</summary>
        public void WriteTo<TState>(IndentedTextWriter writer, TState writeMembersState, Action<IndentedTextWriter, TState> writeMembers)
        {
            WriteTo(writer, writeMembersState, writeMembers, addUnsafe: false);
        }

        /// <summary>Writes containing declarations, adding unsafe only under the legacy memory safety rules.</summary>
        public void WriteToWithUnsafeModifier<TState>(bool useUpdatedMemorySafetyRules, IndentedTextWriter writer, TState writeMembersState, Action<IndentedTextWriter, TState> writeMembers)
        {
            WriteTo(writer, writeMembersState, writeMembers, addUnsafe: !useUpdatedMemorySafetyRules);
        }

        private void WriteTo<TState>(IndentedTextWriter writer, TState state, Action<IndentedTextWriter, TState> writeMembers, bool addUnsafe)
        {
            if (ContainingNamespace is not null)
            {
                writer.WriteLine($"namespace {ContainingNamespace}");
                writer.WriteLine('{');
                writer.Indent++;
            }

            // Containing types are stored innermost-first.
            for (int i = ContainingSyntax.Length - 1; i >= 0; i--)
            {
                DeclarationHeader syntax = ContainingSyntax[i];
                ImmutableArray<string> modifiers = addUnsafe ? CodeWriterHelpers.AddModifier(syntax.Modifiers, "unsafe") : syntax.Modifiers;
                if (!modifiers.IsEmpty)
                {
                    writer.Write(string.Join(" ", modifiers));
                    writer.Write(' ');
                }
                writer.Write($"{syntax.Keyword} ");
                writer.Write(syntax.Name);
                writer.WriteLine();
                writer.WriteLine('{');
                writer.Indent++;
            }

            writeMembers(writer, state);

            for (int i = 0; i < ContainingSyntax.Length; i++)
            {
                writer.Indent--;
                writer.WriteLine('}');
            }
            if (ContainingNamespace is not null)
            {
                writer.Indent--;
                writer.WriteLine('}');
            }
        }

        private static void WriteMember(IndentedTextWriter writer, string member)
        {
            writer.Write(member);
            if (member.Length != 0 && member[member.Length - 1] is not ('\r' or '\n'))
            {
                writer.WriteLine();
            }
        }
    }
}