< Summary

Information
Class: System.Text.Json.JsonSeparatorNamingPolicy
Assembly: System.Text.Json
File(s): C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\Common\JsonSeparatorNamingPolicy.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 87
Coverable lines: 87
Total lines: 179
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 39
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
ConvertName(...)100%110%
ConvertNameCore(...)0%35350%

File(s)

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\Common\JsonSeparatorNamingPolicy.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Globalization;
 7using System.Runtime.CompilerServices;
 8
 9namespace System.Text.Json
 10{
 11    internal abstract class JsonSeparatorNamingPolicy : JsonNamingPolicy
 12    {
 13        private readonly bool _lowercase;
 14        private readonly char _separator;
 15
 016        internal JsonSeparatorNamingPolicy(bool lowercase, char separator)
 017        {
 018            Debug.Assert(char.IsPunctuation(separator));
 19
 020            _lowercase = lowercase;
 021            _separator = separator;
 022        }
 23
 24        public sealed override string ConvertName(string name)
 025        {
 026            ArgumentNullException.ThrowIfNull(name);
 27
 028            return ConvertNameCore(_separator, _lowercase, name.AsSpan());
 029        }
 30
 31        private static string ConvertNameCore(char separator, bool lowercase, ReadOnlySpan<char> chars)
 032        {
 033            char[]? rentedBuffer = null;
 34
 35            // While we can't predict the expansion factor of the resultant string,
 36            // start with a buffer that is at least 20% larger than the input.
 037            int initialBufferLength = (int)(1.2 * chars.Length);
 038            Span<char> destination = initialBufferLength <= JsonConstants.StackallocCharThreshold
 039                ? stackalloc char[JsonConstants.StackallocCharThreshold]
 040                : (rentedBuffer = ArrayPool<char>.Shared.Rent(initialBufferLength));
 41
 042            SeparatorState state = SeparatorState.NotStarted;
 043            int charsWritten = 0;
 44
 045            for (int i = 0; i < chars.Length; i++)
 046            {
 47                // NB this implementation does not handle surrogate pair letters
 48                // cf. https://github.com/dotnet/runtime/issues/90352
 49
 050                char current = chars[i];
 051                UnicodeCategory category = char.GetUnicodeCategory(current);
 52
 053                switch (category)
 54                {
 55                    case UnicodeCategory.UppercaseLetter:
 56
 057                        switch (state)
 58                        {
 59                            case SeparatorState.NotStarted:
 060                                break;
 61
 62                            case SeparatorState.LowercaseLetterOrDigit:
 63                            case SeparatorState.SpaceSeparator:
 64                                // An uppercase letter following a sequence of lowercase letters or spaces
 65                                // denotes the start of a new grouping: emit a separator character.
 066                                WriteChar(separator, ref destination);
 067                                break;
 68
 69                            case SeparatorState.UppercaseLetter:
 70                                // We are reading through a sequence of two or more uppercase letters.
 71                                // Uppercase letters are grouped together with the exception of the
 72                                // final letter, assuming it is followed by lowercase letters.
 73                                // For example, the value 'XMLReader' should render as 'xml_reader',
 74                                // however 'SHA512Hash' should render as 'sha512-hash'.
 075                                if (i + 1 < chars.Length && char.IsLower(chars[i + 1]))
 076                                {
 077                                    WriteChar(separator, ref destination);
 078                                }
 079                                break;
 80
 81                            default:
 082                                Debug.Fail($"Unexpected state {state}");
 83                                break;
 84                        }
 85
 086                        if (lowercase)
 087                        {
 088                            current = char.ToLowerInvariant(current);
 089                        }
 90
 091                        WriteChar(current, ref destination);
 092                        state = SeparatorState.UppercaseLetter;
 093                        break;
 94
 95                    case UnicodeCategory.LowercaseLetter:
 96                    case UnicodeCategory.DecimalDigitNumber:
 97
 098                        if (state is SeparatorState.SpaceSeparator)
 099                        {
 100                            // Normalize preceding spaces to one separator.
 0101                            WriteChar(separator, ref destination);
 0102                        }
 103
 0104                        if (!lowercase && category is UnicodeCategory.LowercaseLetter)
 0105                        {
 0106                            current = char.ToUpperInvariant(current);
 0107                        }
 108
 0109                        WriteChar(current, ref destination);
 0110                        state = SeparatorState.LowercaseLetterOrDigit;
 0111                        break;
 112
 113                    case UnicodeCategory.SpaceSeparator:
 114                        // Space characters are trimmed from the start and end of the input string
 115                        // but are normalized to separator characters if between letters.
 0116                        if (state != SeparatorState.NotStarted)
 0117                        {
 0118                            state = SeparatorState.SpaceSeparator;
 0119                        }
 0120                        break;
 121
 122                    default:
 123                        // Non-alphanumeric characters (including the separator character and surrogates)
 124                        // are written as-is to the output and reset the separator state.
 125                        // E.g. 'ABC???def' maps to 'abc???def' in snake_case.
 126
 0127                        WriteChar(current, ref destination);
 0128                        state = SeparatorState.NotStarted;
 0129                        break;
 130                }
 0131            }
 132
 0133            string result = destination.Slice(0, charsWritten).ToString();
 134
 0135            if (rentedBuffer is not null)
 0136            {
 0137                destination.Slice(0, charsWritten).Clear();
 0138                ArrayPool<char>.Shared.Return(rentedBuffer);
 0139            }
 140
 0141            return result;
 142
 143            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 144            void WriteChar(char value, ref Span<char> destination)
 0145            {
 0146                if (charsWritten == destination.Length)
 0147                {
 0148                    ExpandBuffer(ref destination);
 0149                }
 150
 0151                destination[charsWritten++] = value;
 0152            }
 153
 154            void ExpandBuffer(ref Span<char> destination)
 0155            {
 0156                int newSize = checked(destination.Length * 2);
 0157                char[] newBuffer = ArrayPool<char>.Shared.Rent(newSize);
 0158                destination.CopyTo(newBuffer);
 159
 0160                if (rentedBuffer is not null)
 0161                {
 0162                    destination.Slice(0, charsWritten).Clear();
 0163                    ArrayPool<char>.Shared.Return(rentedBuffer);
 0164                }
 165
 0166                rentedBuffer = newBuffer;
 0167                destination = rentedBuffer;
 0168            }
 0169        }
 170
 171        private enum SeparatorState
 172        {
 173            NotStarted,
 174            UppercaseLetter,
 175            LowercaseLetterOrDigit,
 176            SpaceSeparator,
 177        }
 178    }
 179}