diff --git a/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.cs b/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.cs index d9ba35bb4daf6..66afb43253700 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.cs @@ -36,14 +36,14 @@ public partial interface IChangeToken bool HasChanged { get; } System.IDisposable RegisterChangeCallback(System.Action callback, object? state); } - public readonly partial struct StringSegment : System.IEquatable, System.IEquatable + public readonly partial struct StringSegment : System.IEquatable, System.IEquatable { private readonly object _dummy; private readonly int _dummyPrimitive; public static readonly Microsoft.Extensions.Primitives.StringSegment Empty; - public StringSegment(string buffer) { throw null; } + public StringSegment(string? buffer) { throw null; } public StringSegment(string buffer, int offset, int length) { throw null; } - public string Buffer { get { throw null; } } + public string? Buffer { get { throw null; } } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, nameof(Buffer))] public bool HasValue { get { throw null; } } public char this[int index] { get { throw null; } } @@ -74,7 +74,7 @@ public partial interface IChangeToken public static bool operator ==(Microsoft.Extensions.Primitives.StringSegment left, Microsoft.Extensions.Primitives.StringSegment right) { throw null; } public static implicit operator System.ReadOnlyMemory(Microsoft.Extensions.Primitives.StringSegment segment) { throw null; } public static implicit operator System.ReadOnlySpan(Microsoft.Extensions.Primitives.StringSegment segment) { throw null; } - public static implicit operator Microsoft.Extensions.Primitives.StringSegment(string value) { throw null; } + public static implicit operator Microsoft.Extensions.Primitives.StringSegment(string? value) { throw null; } public static bool operator !=(Microsoft.Extensions.Primitives.StringSegment left, Microsoft.Extensions.Primitives.StringSegment right) { throw null; } public Microsoft.Extensions.Primitives.StringTokenizer Split(char[] chars) { throw null; } public bool StartsWith(string text, System.StringComparison comparisonType) { throw null; } diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs index ef7c4df3909e7..95a84ecd0a5bd 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -10,7 +11,7 @@ namespace Microsoft.Extensions.Primitives /// /// An optimized representation of a substring. /// - public readonly struct StringSegment : IEquatable, IEquatable + public readonly struct StringSegment : IEquatable, IEquatable { /// /// A for . @@ -23,7 +24,7 @@ namespace Microsoft.Extensions.Primitives /// /// The original . The includes the whole . /// - public StringSegment(string buffer) + public StringSegment(string? buffer) { Buffer = buffer; Offset = 0; @@ -62,7 +63,7 @@ public StringSegment(string buffer, int offset, int length) /// /// Gets the buffer for this . /// - public string Buffer { get; } + public string? Buffer { get; } /// /// Gets the offset within the buffer for this . @@ -102,6 +103,7 @@ public char this[int index] ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); } + Debug.Assert(Buffer is not null); return Buffer[Offset + index]; } } @@ -231,20 +233,14 @@ public bool Equals(StringSegment other, StringComparison comparisonType) /// The second to compare. /// One of the enumeration values that specifies the rules for the comparison. /// if the objects are equal; otherwise, . - public static bool Equals(StringSegment a, StringSegment b, StringComparison comparisonType) - { - return a.Equals(b, comparisonType); - } + public static bool Equals(StringSegment a, StringSegment b, StringComparison comparisonType) => a.Equals(b, comparisonType); /// /// Checks if the specified is equal to the current . /// /// The to compare with the current . /// if the specified is equal to the current ; otherwise, . - public bool Equals([NotNullWhen(true)] string? text) - { - return Equals(text, StringComparison.Ordinal); - } + public bool Equals([NotNullWhen(true)] string? text) => Equals(text, StringComparison.Ordinal); /// /// Checks if the specified is equal to the current . @@ -255,15 +251,10 @@ public bool Equals([NotNullWhen(true)] string? text) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals([NotNullWhen(true)] string? text, StringComparison comparisonType) { - if (text == null) - { - return false; - } - - if (!HasValue) + if (!HasValue || text == null) { CheckStringComparison(comparisonType); // must arg check before returning - return false; + return text == Buffer; // return true if both are null } return AsSpan().Equals(text.AsSpan(), comparisonType); @@ -311,7 +302,7 @@ public override int GetHashCode() /// Creates a new from the given . /// /// The to convert to a - public static implicit operator StringSegment(string value) => new StringSegment(value); + public static implicit operator StringSegment(string? value) => new StringSegment(value); /// /// Creates a see from the given . @@ -732,14 +723,5 @@ Exception GetInvalidArgumentsException(bool hasValue) return ThrowHelper.GetArgumentException(ExceptionResource.Argument_InvalidOffsetLengthStringSegment); } } - - /// - bool IEquatable.Equals(string? other) - { - // Explicit interface implementation for IEquatable because - // the interface's Equals method allows null strings, which we return - // as not-equal. - return other != null && Equals(other); - } } } diff --git a/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs b/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs index 812a154e3bfc9..7271103d7b211 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs @@ -170,6 +170,9 @@ public void StringSegment_StringCtor_AllowsNullBuffers() Assert.False(segment.HasValue); Assert.Equal(0, segment.Offset); Assert.Equal(0, segment.Length); + Assert.Null(segment.Buffer); + Assert.Null(segment.Value); + Assert.Throws(() => segment[0]); } [Fact] @@ -315,26 +318,20 @@ public void StringSegment_Indexer_OutOfRangeThrows(string value, int offset, int Assert.Throws(() => segment[index]); } - public static TheoryData EndsWithData + // candidate / comparer / expected result + public static TheoryData EndsWithData => new() { - get - { - // candidate / comparer / expected result - return new TheoryData() - { - { "Hello", StringComparison.Ordinal, false }, - { "ello ", StringComparison.Ordinal, false }, - { "ll", StringComparison.Ordinal, false }, - { "ello", StringComparison.Ordinal, true }, - { "llo", StringComparison.Ordinal, true }, - { "lo", StringComparison.Ordinal, true }, - { "o", StringComparison.Ordinal, true }, - { string.Empty, StringComparison.Ordinal, true }, - { "eLLo", StringComparison.Ordinal, false }, - { "eLLo", StringComparison.OrdinalIgnoreCase, true }, - }; - } - } + { "Hello", StringComparison.Ordinal, false }, + { "ello ", StringComparison.Ordinal, false }, + { "ll", StringComparison.Ordinal, false }, + { "ello", StringComparison.Ordinal, true }, + { "llo", StringComparison.Ordinal, true }, + { "lo", StringComparison.Ordinal, true }, + { "o", StringComparison.Ordinal, true }, + { string.Empty, StringComparison.Ordinal, true }, + { "eLLo", StringComparison.Ordinal, false }, + { "eLLo", StringComparison.OrdinalIgnoreCase, true }, + }; [Theory] [MemberData(nameof(EndsWithData))] @@ -384,26 +381,20 @@ public void StringSegment_EndsWith_String_InvalidComparisonType_Throws() Assert.Throws("comparisonType", () => segment.EndsWith(string.Empty, (StringComparison)6)); } - public static TheoryData StartsWithData + // candidate / comparer / expected result + public static TheoryData StartsWithData => new() { - get - { - // candidate / comparer / expected result - return new TheoryData() - { - { "Hello", StringComparison.Ordinal, false }, - { "ello ", StringComparison.Ordinal, false }, - { "ll", StringComparison.Ordinal, false }, - { "ello", StringComparison.Ordinal, true }, - { "ell", StringComparison.Ordinal, true }, - { "el", StringComparison.Ordinal, true }, - { "e", StringComparison.Ordinal, true }, - { string.Empty, StringComparison.Ordinal, true }, - { "eLLo", StringComparison.Ordinal, false }, - { "eLLo", StringComparison.OrdinalIgnoreCase, true }, - }; - } - } + { "Hello", StringComparison.Ordinal, false }, + { "ello ", StringComparison.Ordinal, false }, + { "ll", StringComparison.Ordinal, false }, + { "ello", StringComparison.Ordinal, true }, + { "ell", StringComparison.Ordinal, true }, + { "el", StringComparison.Ordinal, true }, + { "e", StringComparison.Ordinal, true }, + { string.Empty, StringComparison.Ordinal, true }, + { "eLLo", StringComparison.Ordinal, false }, + { "eLLo", StringComparison.OrdinalIgnoreCase, true }, + }; [Theory] [MemberData(nameof(StartsWithData))] @@ -453,20 +444,14 @@ public void StringSegment_StartsWith_String_InvalidComparisonType_Throws() Assert.Throws("comparisonType", () => segment.StartsWith(string.Empty, (StringComparison)6)); } - public static TheoryData EqualsStringData + // candidate / comparer / expected result + public static TheoryData EqualsStringData =>new() { - get - { - // candidate / comparer / expected result - return new TheoryData() - { - { "eLLo", StringComparison.OrdinalIgnoreCase, true }, - { "eLLo", StringComparison.Ordinal, false }, - { null, StringComparison.OrdinalIgnoreCase, false }, - { null, StringComparison.Ordinal, false }, - }; - } - } + { "eLLo", StringComparison.OrdinalIgnoreCase, true }, + { "eLLo", StringComparison.Ordinal, false }, + { null, StringComparison.OrdinalIgnoreCase, false }, + { null, StringComparison.Ordinal, false }, + }; [Theory] [MemberData(nameof(EqualsStringData))] @@ -482,6 +467,31 @@ public void StringSegment_Equals_String_Valid(string candidate, StringComparison Assert.Equal(expectedResult, result); } + // candidate / comparer / expected result + public static TheoryData NullEqualsStringData => new() + { + { null, StringComparison.OrdinalIgnoreCase, true }, + { null, StringComparison.Ordinal, true }, + { "eLLo", StringComparison.OrdinalIgnoreCase, false }, + { "eLLo", StringComparison.Ordinal, false }, + { string.Empty, StringComparison.OrdinalIgnoreCase, false }, + { string.Empty, StringComparison.Ordinal, false }, + }; + + [Theory] + [MemberData(nameof(NullEqualsStringData))] + public void StringSegment_Equals_NullString_Valid(string candidate, StringComparison comparison, bool expectedResult) + { + // Arrange + var segment = new StringSegment(null); + + // Act + bool result = segment.Equals(candidate, comparison); + + // Act & assert + Assert.Equal(expectedResult, result); + } + [Fact] public void StringSegment_Equals_String_InvalidComparisonType_Throws() { @@ -561,21 +571,15 @@ public void StringSegment_IsNullOrEmpty_Invalid() Assert.False(StringSegment.IsNullOrEmpty(new StringSegment("ABCDefg", 3, 2))); } - public static TheoryData GetHashCode_ReturnsSameValueForEqualSubstringsData + public static TheoryData GetHashCode_ReturnsSameValueForEqualSubstringsData => new TheoryData { - get - { - return new TheoryData - { - { default(StringSegment), default(StringSegment) }, - { default(StringSegment), new StringSegment() }, - { new StringSegment("Test123", 0, 0), new StringSegment(string.Empty) }, - { new StringSegment("C`est si bon", 2, 3), new StringSegment("Yesterday", 1, 3) }, - { new StringSegment("Hello", 1, 4), new StringSegment("Hello world", 1, 4) }, - { new StringSegment("Hello"), new StringSegment("Hello", 0, 5) }, - }; - } - } + { default(StringSegment), default(StringSegment) }, + { default(StringSegment), new StringSegment() }, + { new StringSegment("Test123", 0, 0), new StringSegment(string.Empty) }, + { new StringSegment("C`est si bon", 2, 3), new StringSegment("Yesterday", 1, 3) }, + { new StringSegment("Hello", 1, 4), new StringSegment("Hello world", 1, 4) }, + { new StringSegment("Hello"), new StringSegment("Hello", 0, 5) }, + }; [Theory] [MemberData(nameof(GetHashCode_ReturnsSameValueForEqualSubstringsData))] @@ -631,18 +635,12 @@ public void StringSegment_EqualsString_Invalid() Assert.False(result); } - public static TheoryData DefaultStringSegmentEqualsStringSegmentData + // candidate + public static TheoryData DefaultStringSegmentEqualsStringSegmentData => new() { - get - { - // candidate - return new TheoryData() - { - { default(StringSegment) }, - { new StringSegment() }, - }; - } - } + { default(StringSegment) }, + { new StringSegment() }, + }; [Theory] [MemberData(nameof(DefaultStringSegmentEqualsStringSegmentData))] @@ -658,19 +656,13 @@ public void DefaultStringSegment_EqualsStringSegment(StringSegment candidate) Assert.True(result); } - public static TheoryData DefaultStringSegmentDoesNotEqualStringSegmentData + // candidate + public static TheoryData DefaultStringSegmentDoesNotEqualStringSegmentData => new() { - get - { - // candidate - return new TheoryData() - { - { new StringSegment("Hello, World!", 1, 4) }, - { new StringSegment("Hello", 1, 0) }, - { new StringSegment(string.Empty) }, - }; - } - } + { new StringSegment("Hello, World!", 1, 4) }, + { new StringSegment("Hello", 1, 0) }, + { new StringSegment(string.Empty) }, + }; [Theory] [MemberData(nameof(DefaultStringSegmentDoesNotEqualStringSegmentData))] @@ -686,18 +678,12 @@ public void DefaultStringSegment_DoesNotEqualStringSegment(StringSegment candida Assert.False(result); } - public static TheoryData DefaultStringSegmentDoesNotEqualStringData + // candidate + public static TheoryData DefaultStringSegmentDoesNotEqualStringData => new() { - get - { - // candidate - return new TheoryData() - { - { string.Empty }, - { "Hello, World!" }, - }; - } - } + { string.Empty }, + { "Hello, World!" }, + }; [Theory] [MemberData(nameof(DefaultStringSegmentDoesNotEqualStringData))] @@ -713,22 +699,16 @@ public void DefaultStringSegment_DoesNotEqualString(string candidate) Assert.False(result); } - public static TheoryData EqualsStringSegmentData + // candidate / comparer / expected result + public static TheoryData EqualsStringSegmentData => new() { - get - { - // candidate / comparer / expected result - return new TheoryData() - { - { new StringSegment("Hello, World!", 1, 4), StringComparison.Ordinal, true }, - { new StringSegment("HELlo, World!", 1, 4), StringComparison.Ordinal, false }, - { new StringSegment("HELlo, World!", 1, 4), StringComparison.OrdinalIgnoreCase, true }, - { new StringSegment("ello, World!", 0, 4), StringComparison.Ordinal, true }, - { new StringSegment("ello, World!", 0, 3), StringComparison.Ordinal, false }, - { new StringSegment("ello, World!", 1, 3), StringComparison.Ordinal, false }, - }; - } - } + { new StringSegment("Hello, World!", 1, 4), StringComparison.Ordinal, true }, + { new StringSegment("HELlo, World!", 1, 4), StringComparison.Ordinal, false }, + { new StringSegment("HELlo, World!", 1, 4), StringComparison.OrdinalIgnoreCase, true }, + { new StringSegment("ello, World!", 0, 4), StringComparison.Ordinal, true }, + { new StringSegment("ello, World!", 0, 3), StringComparison.Ordinal, false }, + { new StringSegment("ello, World!", 1, 3), StringComparison.Ordinal, false }, + }; [Theory] [MemberData(nameof(EqualsStringSegmentData))] @@ -942,19 +922,13 @@ public void StringSegment_Subsegment_OffsetAndLengthOverflows() Assert.Contains("bounds", exception.Message); } - public static TheoryData CompareLesserData + // candidate / comparer + public static TheoryData CompareLesserData => new() { - get - { - // candidate / comparer - return new TheoryData() - { - { new StringSegment("abcdef", 1, 4), StringSegmentComparer.Ordinal }, - { new StringSegment("abcdef", 1, 5), StringSegmentComparer.OrdinalIgnoreCase }, - { new StringSegment("ABCDEF", 2, 2), StringSegmentComparer.OrdinalIgnoreCase }, - }; - } - } + { new StringSegment("abcdef", 1, 4), StringSegmentComparer.Ordinal }, + { new StringSegment("abcdef", 1, 5), StringSegmentComparer.OrdinalIgnoreCase }, + { new StringSegment("ABCDEF", 2, 2), StringSegmentComparer.OrdinalIgnoreCase }, + }; [Theory] [MemberData(nameof(CompareLesserData))] @@ -970,20 +944,14 @@ public void StringSegment_Compare_Lesser(StringSegment candidate, StringSegmentC Assert.True(result < 0, $"{segment} should be less than {candidate}"); } - public static TheoryData CompareEqualData + // candidate / comparer + public static TheoryData CompareEqualData => new() { - get - { - // candidate / comparer - return new TheoryData() - { - { new StringSegment("abcdef", 1, 4), StringSegmentComparer.Ordinal }, - { new StringSegment("ABCDEF", 1, 4), StringSegmentComparer.OrdinalIgnoreCase }, - { new StringSegment("bcde", 0, 4), StringSegmentComparer.Ordinal }, - { new StringSegment("BcDeF", 0, 4), StringSegmentComparer.OrdinalIgnoreCase }, - }; - } - } + { new StringSegment("abcdef", 1, 4), StringSegmentComparer.Ordinal }, + { new StringSegment("ABCDEF", 1, 4), StringSegmentComparer.OrdinalIgnoreCase }, + { new StringSegment("bcde", 0, 4), StringSegmentComparer.Ordinal }, + { new StringSegment("BcDeF", 0, 4), StringSegmentComparer.OrdinalIgnoreCase }, + }; [Theory] [MemberData(nameof(CompareEqualData))] @@ -999,19 +967,13 @@ public void StringSegment_Compare_Equal(StringSegment candidate, StringSegmentCo Assert.True(result == 0, $"{segment} should equal {candidate}"); } - public static TheoryData CompareGreaterData + // candidate / comparer + public static TheoryData CompareGreaterData => new() { - get - { - // candidate / comparer - return new TheoryData() - { - { new StringSegment("ABCDEF", 1, 4), StringSegmentComparer.Ordinal }, - { new StringSegment("ABCDEF", 0, 6), StringSegmentComparer.OrdinalIgnoreCase }, - { new StringSegment("abcdef", 0, 3), StringSegmentComparer.Ordinal }, - }; - } - } + { new StringSegment("ABCDEF", 1, 4), StringSegmentComparer.Ordinal }, + { new StringSegment("ABCDEF", 0, 6), StringSegmentComparer.OrdinalIgnoreCase }, + { new StringSegment("abcdef", 0, 3), StringSegmentComparer.Ordinal }, + }; [Theory] [MemberData(nameof(CompareGreaterData))] @@ -1411,28 +1373,22 @@ public void TrimEnd_RemovesTrailingWhitespaces(string value, int start, int leng Assert.Equal(expected, actual.Value); } - public static TheoryData GlobalizationCompareTestData + public static TheoryData GlobalizationCompareTestData => new() { - get - { - return new() - { - { null, string.Empty, StringComparison.Ordinal, -1 }, // null always compares before non-null - { null, string.Empty, StringComparison.InvariantCultureIgnoreCase, -1 }, // null always compares before non-null - { null, null, StringComparison.Ordinal, 0 }, - { null, null, StringComparison.InvariantCultureIgnoreCase, 0 }, - { string.Empty, null, StringComparison.Ordinal, 1 }, - { string.Empty, null, StringComparison.InvariantCultureIgnoreCase, 1 }, - { "x\u00E9y", "xE\u0301y", StringComparison.InvariantCulture, - PlatformDetection.IsInvariantGlobalization ? 1 : -1 }, // linguistic: lowercase sorts before uppercase - { "x\u00E9y", "xE\u0301y", StringComparison.InvariantCultureIgnoreCase, - PlatformDetection.IsInvariantGlobalization ? 1 : 0 }, // equal (linguistic, one is normalized) - { "Hello", "HELLO", StringComparison.InvariantCulture, - PlatformDetection.IsInvariantGlobalization ? 1 : -1 }, // linguistic: lowercase sorts before uppercase - { "Hello", "HELLO", StringComparison.InvariantCultureIgnoreCase, 0 }, - }; - } - } + { null, string.Empty, StringComparison.Ordinal, -1 }, // null always compares before non-null + { null, string.Empty, StringComparison.InvariantCultureIgnoreCase, -1 }, // null always compares before non-null + { null, null, StringComparison.Ordinal, 0 }, + { null, null, StringComparison.InvariantCultureIgnoreCase, 0 }, + { string.Empty, null, StringComparison.Ordinal, 1 }, + { string.Empty, null, StringComparison.InvariantCultureIgnoreCase, 1 }, + { "x\u00E9y", "xE\u0301y", StringComparison.InvariantCulture, + PlatformDetection.IsInvariantGlobalization ? 1 : -1 }, // linguistic: lowercase sorts before uppercase + { "x\u00E9y", "xE\u0301y", StringComparison.InvariantCultureIgnoreCase, + PlatformDetection.IsInvariantGlobalization ? 1 : 0 }, // equal (linguistic, one is normalized) + { "Hello", "HELLO", StringComparison.InvariantCulture, + PlatformDetection.IsInvariantGlobalization ? 1 : -1 }, // linguistic: lowercase sorts before uppercase + { "Hello", "HELLO", StringComparison.InvariantCultureIgnoreCase, 0 }, + }; [Theory] [MemberData(nameof(GlobalizationCompareTestData))] @@ -1477,50 +1433,37 @@ public void StringSegment_CompareEqual_Globalized(string a, string b, StringComp // StringSegment.Equals(string, ...) and IEquatable.Equals { - if (b == null) - { - Assert.False(((IEquatable)sa).Equals(b)); // null string never equal, not even to null StringSegment - } - else + bool areEqual = sa.Equals(b, comparisonType); + Assert.Equal(expectedCompareToSign == 0, areEqual); + + if (comparisonType == StringComparison.Ordinal) { - bool areEqual = sa.Equals(b, comparisonType); + areEqual = sa.Equals(b); Assert.Equal(expectedCompareToSign == 0, areEqual); - if (comparisonType == StringComparison.Ordinal) - { - areEqual = sa.Equals(b); - Assert.Equal(expectedCompareToSign == 0, areEqual); - - areEqual = ((IEquatable)sa).Equals(b); - Assert.Equal(expectedCompareToSign == 0, areEqual); - } + areEqual = ((IEquatable)sa).Equals(b); + Assert.Equal(expectedCompareToSign == 0, areEqual); } } } - public static TheoryData GlobalizationStartsWithData - { - get - { - return new() - { - { null, "\u200d", StringComparison.Ordinal, false }, // null never starts with anything - { null, "\u200d", StringComparison.InvariantCulture, false }, // null never starts with anything - { null, string.Empty, StringComparison.Ordinal, false }, // null never starts with anything - { string.Empty, string.Empty, StringComparison.Ordinal, true }, // not char-for-char equivalent - { string.Empty, "\u200d", StringComparison.Ordinal, false }, // not char-for-char equivalent - { string.Empty, "\u200d", StringComparison.InvariantCulture, - PlatformDetection.IsInvariantGlobalization ? false : true }, // linguistic: ZWJ is zero-weight, occurs at all indices - { "\u200d", string.Empty, StringComparison.Ordinal, true }, // all strings trivially start with the empty string - { "\u200d", "\u200d\u200d", StringComparison.InvariantCulture, - PlatformDetection.IsInvariantGlobalization ? false : true }, // linguistic: ZWJ is zero-weight - { "Hello", "h", StringComparison.Ordinal, false }, - { "Hello", "h", StringComparison.OrdinalIgnoreCase, true }, - { "Hello", "hi", StringComparison.Ordinal, false }, - { "Hello", "hi", StringComparison.OrdinalIgnoreCase, false }, - }; - } - } + public static TheoryData GlobalizationStartsWithData => new() + { + { null, "\u200d", StringComparison.Ordinal, false }, // null never starts with anything + { null, "\u200d", StringComparison.InvariantCulture, false }, // null never starts with anything + { null, string.Empty, StringComparison.Ordinal, false }, // null never starts with anything + { string.Empty, string.Empty, StringComparison.Ordinal, true }, // not char-for-char equivalent + { string.Empty, "\u200d", StringComparison.Ordinal, false }, // not char-for-char equivalent + { string.Empty, "\u200d", StringComparison.InvariantCulture, + PlatformDetection.IsInvariantGlobalization ? false : true }, // linguistic: ZWJ is zero-weight, occurs at all indices + { "\u200d", string.Empty, StringComparison.Ordinal, true }, // all strings trivially start with the empty string + { "\u200d", "\u200d\u200d", StringComparison.InvariantCulture, + PlatformDetection.IsInvariantGlobalization ? false : true }, // linguistic: ZWJ is zero-weight + { "Hello", "h", StringComparison.Ordinal, false }, + { "Hello", "h", StringComparison.OrdinalIgnoreCase, true }, + { "Hello", "hi", StringComparison.Ordinal, false }, + { "Hello", "hi", StringComparison.OrdinalIgnoreCase, false }, + }; [Theory] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "netfx has some IsPrefix / IsSuffix globalization bugs.")] @@ -1543,29 +1486,23 @@ public void StringSegment_StartsWith_Globalized(string a, string b, StringCompar Assert.Equal(expectedResult, actualResult); } - public static TheoryData GlobalizationEndsWithData - { - get - { - return new() - { - { null, "\u200d", StringComparison.Ordinal, false }, // null never ends with anything - { null, "\u200d", StringComparison.InvariantCulture, false }, // null never ends with anything - { null, string.Empty, StringComparison.Ordinal, false }, // null never ends with anything - { string.Empty, string.Empty, StringComparison.Ordinal, true }, // not char-for-char equivalent - { string.Empty, "\u200d", StringComparison.Ordinal, false }, // not char-for-char equivalent - { string.Empty, "\u200d", StringComparison.InvariantCulture, - PlatformDetection.IsInvariantGlobalization ? false : true }, // linguistic: ZWJ is zero-weight, occurs at all indices - { "\u200d", string.Empty, StringComparison.Ordinal, true }, // all strings trivially ends with the empty string - { "\u200d", "\u200d\u200d", StringComparison.InvariantCulture, - PlatformDetection.IsInvariantGlobalization ? false : true }, // linguistic: ZWJ is zero-weight - { "HELLO", "o", StringComparison.Ordinal, false }, - { "HELLO", "o", StringComparison.OrdinalIgnoreCase, true }, - { "HELLO", "illo", StringComparison.Ordinal, false }, - { "HELLO", "illo", StringComparison.OrdinalIgnoreCase, false }, - }; - } - } + public static TheoryData GlobalizationEndsWithData => new() + { + { null, "\u200d", StringComparison.Ordinal, false }, // null never ends with anything + { null, "\u200d", StringComparison.InvariantCulture, false }, // null never ends with anything + { null, string.Empty, StringComparison.Ordinal, false }, // null never ends with anything + { string.Empty, string.Empty, StringComparison.Ordinal, true }, // not char-for-char equivalent + { string.Empty, "\u200d", StringComparison.Ordinal, false }, // not char-for-char equivalent + { string.Empty, "\u200d", StringComparison.InvariantCulture, + PlatformDetection.IsInvariantGlobalization ? false : true }, // linguistic: ZWJ is zero-weight, occurs at all indices + { "\u200d", string.Empty, StringComparison.Ordinal, true }, // all strings trivially ends with the empty string + { "\u200d", "\u200d\u200d", StringComparison.InvariantCulture, + PlatformDetection.IsInvariantGlobalization ? false : true }, // linguistic: ZWJ is zero-weight + { "HELLO", "o", StringComparison.Ordinal, false }, + { "HELLO", "o", StringComparison.OrdinalIgnoreCase, true }, + { "HELLO", "illo", StringComparison.Ordinal, false }, + { "HELLO", "illo", StringComparison.OrdinalIgnoreCase, false }, + }; [Theory] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "netfx has some IsPrefix / IsSuffix globalization bugs.")]