File: System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Comment.cs
Web Access
Project: src\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Diagnostics;
 
namespace System.Text.Json
{
    public sealed partial class Utf8JsonWriter
    {
        private static readonly char[] s_singleLineCommentDelimiter = new char[2] { '*', '/' };
        private static ReadOnlySpan<byte> SingleLineCommentDelimiterUtf8 => "*/"u8;
 
        /// <summary>
        /// Writes the string text value (as a JSON comment).
        /// </summary>
        /// <param name="value">The value to write as a JSON comment within /*..*/.</param>
        /// <exception cref="ArgumentException">
        /// Thrown when the specified value is too large OR if the given string text value contains a comment delimiter (that is, */).
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="value"/> parameter is <see langword="null"/>.
        /// </exception>
        /// <remarks>
        /// The comment value is not escaped before writing.
        /// </remarks>
        public void WriteCommentValue(string value)
        {
            if (value is null)
            {
                ThrowHelper.ThrowArgumentNullException(nameof(value));
            }
            WriteCommentValue(value.AsSpan());
        }
 
        /// <summary>
        /// Writes the text value (as a JSON comment).
        /// </summary>
        /// <param name="value">The value to write as a JSON comment within /*..*/.</param>
        /// <exception cref="ArgumentException">
        /// Thrown when the specified value is too large OR if the given text value contains a comment delimiter (that is, */).
        /// </exception>
        /// <remarks>
        /// The comment value is not escaped before writing.
        /// </remarks>
        public void WriteCommentValue(ReadOnlySpan<char> value)
        {
            JsonWriterHelper.ValidateValue(value);
 
            if (value.IndexOf(s_singleLineCommentDelimiter) != -1)
            {
                ThrowHelper.ThrowArgumentException_InvalidCommentValue();
            }
 
            WriteCommentByOptions(value);
            if (_tokenType is JsonTokenType.PropertyName or JsonTokenType.None)
            {
                _commentAfterNoneOrPropertyName = true;
            }
        }
 
        private void WriteCommentByOptions(ReadOnlySpan<char> value)
        {
            if (_options.Indented)
            {
                WriteCommentIndented(value);
            }
            else
            {
                WriteCommentMinimized(value);
            }
        }
 
        private void WriteCommentMinimized(ReadOnlySpan<char> value)
        {
            Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 4);
 
            // All ASCII, /*...*/ => escapedValue.Length + 4
            // Optionally, up to 3x growth when transcoding
            int maxRequired = (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4;
 
            if (_memory.Length - BytesPending < maxRequired)
            {
                Grow(maxRequired);
            }
 
            Span<byte> output = _memory.Span;
 
            output[BytesPending++] = JsonConstants.Slash;
            output[BytesPending++] = JsonConstants.Asterisk;
 
            OperationStatus status = JsonWriterHelper.ToUtf8(value, output.Slice(BytesPending), out int written);
            Debug.Assert(status != OperationStatus.DestinationTooSmall);
            if (status == OperationStatus.InvalidData)
            {
                ThrowHelper.ThrowArgumentException_InvalidUTF16(value[written]);
            }
 
            BytesPending += written;
 
            output[BytesPending++] = JsonConstants.Asterisk;
            output[BytesPending++] = JsonConstants.Slash;
        }
 
        private void WriteCommentIndented(ReadOnlySpan<char> value)
        {
            int indent = Indentation;
            Debug.Assert(indent <= _indentLength * _options.MaxDepth);
 
            Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 4 - _newLineLength);
 
            // All ASCII, /*...*/ => escapedValue.Length + 4
            // Optionally, 1-2 bytes for new line, and up to 3x growth when transcoding
            int maxRequired = indent + (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4 + _newLineLength;
 
            if (_memory.Length - BytesPending < maxRequired)
            {
                Grow(maxRequired);
            }
 
            Span<byte> output = _memory.Span;
 
            if (_tokenType != JsonTokenType.None || _commentAfterNoneOrPropertyName)
            {
                WriteNewLine(output);
                WriteIndentation(output.Slice(BytesPending), indent);
                BytesPending += indent;
            }
 
            output[BytesPending++] = JsonConstants.Slash;
            output[BytesPending++] = JsonConstants.Asterisk;
 
            OperationStatus status = JsonWriterHelper.ToUtf8(value, output.Slice(BytesPending), out int written);
            Debug.Assert(status != OperationStatus.DestinationTooSmall);
            if (status == OperationStatus.InvalidData)
            {
                ThrowHelper.ThrowArgumentException_InvalidUTF16(value[written]);
            }
 
            BytesPending += written;
 
            output[BytesPending++] = JsonConstants.Asterisk;
            output[BytesPending++] = JsonConstants.Slash;
        }
 
        /// <summary>
        /// Writes the UTF-8 text value (as a JSON comment).
        /// </summary>
        /// <param name="utf8Value">The UTF-8 encoded value to be written as a JSON comment within /*..*/.</param>
        /// <remarks>
        /// The comment value is not escaped before writing.
        /// </remarks>
        /// <exception cref="ArgumentException">
        /// Thrown when the specified value is too large OR if the given UTF-8 text value contains a comment delimiter (that is, */).
        /// </exception>
        public void WriteCommentValue(ReadOnlySpan<byte> utf8Value)
        {
            JsonWriterHelper.ValidateValue(utf8Value);
 
            if (utf8Value.IndexOf(SingleLineCommentDelimiterUtf8) != -1)
            {
                ThrowHelper.ThrowArgumentException_InvalidCommentValue();
            }
 
            if (!JsonWriterHelper.IsValidUtf8String(utf8Value))
            {
                ThrowHelper.ThrowArgumentException_InvalidUTF8(utf8Value);
            }
 
            WriteCommentByOptions(utf8Value);
            if (_tokenType is JsonTokenType.PropertyName or JsonTokenType.None)
            {
                _commentAfterNoneOrPropertyName = true;
            }
        }
 
        private void WriteCommentByOptions(ReadOnlySpan<byte> utf8Value)
        {
            if (_options.Indented)
            {
                WriteCommentIndented(utf8Value);
            }
            else
            {
                WriteCommentMinimized(utf8Value);
            }
        }
 
        private void WriteCommentMinimized(ReadOnlySpan<byte> utf8Value)
        {
            Debug.Assert(utf8Value.Length < int.MaxValue - 4);
 
            int maxRequired = utf8Value.Length + 4; // /*...*/
 
            if (_memory.Length - BytesPending < maxRequired)
            {
                Grow(maxRequired);
            }
 
            Span<byte> output = _memory.Span;
 
            output[BytesPending++] = JsonConstants.Slash;
            output[BytesPending++] = JsonConstants.Asterisk;
 
            utf8Value.CopyTo(output.Slice(BytesPending));
            BytesPending += utf8Value.Length;
 
            output[BytesPending++] = JsonConstants.Asterisk;
            output[BytesPending++] = JsonConstants.Slash;
        }
 
        private void WriteCommentIndented(ReadOnlySpan<byte> utf8Value)
        {
            int indent = Indentation;
            Debug.Assert(indent <= _indentLength * _options.MaxDepth);
 
            Debug.Assert(utf8Value.Length < int.MaxValue - indent - 4 - _newLineLength);
 
            int minRequired = indent + utf8Value.Length + 4; // /*...*/
            int maxRequired = minRequired + _newLineLength; // Optionally, 1-2 bytes for new line
 
            if (_memory.Length - BytesPending < maxRequired)
            {
                Grow(maxRequired);
            }
 
            Span<byte> output = _memory.Span;
 
            if (_tokenType != JsonTokenType.None || _commentAfterNoneOrPropertyName)
            {
                WriteNewLine(output);
 
                WriteIndentation(output.Slice(BytesPending), indent);
                BytesPending += indent;
            }
 
            output[BytesPending++] = JsonConstants.Slash;
            output[BytesPending++] = JsonConstants.Asterisk;
 
            utf8Value.CopyTo(output.Slice(BytesPending));
            BytesPending += utf8Value.Length;
 
            output[BytesPending++] = JsonConstants.Asterisk;
            output[BytesPending++] = JsonConstants.Slash;
        }
    }
}