diff --git a/docs/design/features/globalization-hybrid-mode.md b/docs/design/features/globalization-hybrid-mode.md index 2270fe897c1c09..bdbdb27b09e425 100644 --- a/docs/design/features/globalization-hybrid-mode.md +++ b/docs/design/features/globalization-hybrid-mode.md @@ -270,13 +270,12 @@ Dependencies: Web API does not expose locale-sensitive endsWith/startsWith function. As a workaround, both strings get normalized and weightless characters are removed. Resulting strings are cut to the same length and comparison is performed. This approach, beyond having the same compare option limitations as described under **String comparison**, has additional limitations connected with the workaround used. Because we are normalizing strings to be able to cut them, we cannot calculate the match length on the original strings. Methods that calculate this information throw PlatformNotSupported exception: -- [CompareInfo.IsPrefix](https://learn.microsoft.com/dotnet/api/system.globalization.compareinfo.isprefix?view=net-8.0#system-globalization-compareinfo-isprefix(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) -- [CompareInfo.IsSuffix](https://learn.microsoft.com/dotnet/api/system.globalization.compareinfo.issuffix?view=net-8.0#system-globalization-compareinfo-issuffix(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) +- [CompareInfo.IsPrefix](https://learn.microsoft.com/dotnet/api/system.globalization.compareinfo.isprefix?view=#system-globalization-compareinfo-isprefix(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) +- [CompareInfo.IsSuffix](https://learn.microsoft.com/dotnet/api/system.globalization.compareinfo.issuffix?view=system-globalization-compareinfo-issuffix(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) - `IgnoreSymbols` Only comparisons that do not skip character types are allowed. E.g. `IgnoreSymbols` skips symbol-chars in comparison/indexing. All `CompareOptions` combinations that include `IgnoreSymbols` throw `PlatformNotSupportedException`. - **String indexing** Affected public APIs: @@ -287,6 +286,15 @@ Affected public APIs: Web API does not expose locale-sensitive indexing function. There is a discussion on adding it: https://github.com/tc39/ecma402/issues/506. In the current state, as a workaround, locale-sensitive string segmenter combined with locale-sensitive comparison is used. This approach, beyond having the same compare option limitations as described under **String comparison**, has additional limitations connected with the workaround used. Information about additional limitations: +- Methods that calculate `matchLength` always return throw PlatformNotSupported exception: + +[CompareInfo.IndexOf](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.compareinfo.indexof?view=system-globalization-compareinfo-indexof(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) + +[CompareInfo.LastIndexOf](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.compareinfo.lastindexof?view=system-globalization-compareinfo-lastindexof(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) + +- String.Replace that uses `StringComparison` argument relies internally on IndexOf with `matchLength` argument. From this reason, it throws PlatformNotSupportedException: +[String.Replace](https://learn.microsoft.com/en-us/dotnet/api/system.string.replace?view=system-string-replace(system-string-system-string-system-stringcomparison)) + - Support depends on [`Intl.segmenter's support`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter#browser_compatibility). - `IgnoreSymbols` @@ -463,6 +471,11 @@ Affected public APIs: - String.IndexOf - String.LastIndexOf +Methods that calculate `matchLength` throw PlatformNotSupported exception: +[CompareInfo.IndexOf](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.compareinfo.indexof?view=system-globalization-compareinfo-indexof(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) + +[CompareInfo.LastIndexOf](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.compareinfo.lastindexof?view=system-globalization-compareinfo-lastindexof(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) + Mapped to Apple Native API `rangeOfString:options:range:locale:`(https://developer.apple.com/documentation/foundation/nsstring/1417348-rangeofstring?language=objc) In `rangeOfString:options:range:locale:` objects are compared by checking the Unicode canonical equivalence of their code point sequences. diff --git a/src/libraries/Common/tests/Tests/System/StringTests.cs b/src/libraries/Common/tests/Tests/System/StringTests.cs index 709d7c9847403e..b756c06d87d6f6 100644 --- a/src/libraries/Common/tests/Tests/System/StringTests.cs +++ b/src/libraries/Common/tests/Tests/System/StringTests.cs @@ -1730,7 +1730,6 @@ public static IEnumerable EndsWith_StringComparison_TestData() [Theory] [MemberData(nameof(EndsWith_StringComparison_TestData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95473", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public static void EndsWith_StringComparison(string s, string value, StringComparison comparisonType, bool expected) { if (comparisonType == StringComparison.CurrentCulture) @@ -4925,9 +4924,7 @@ public static IEnumerable StartsWith_StringComparison_TestData() if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { - // "https://github.com/dotnet/runtime/issues/95473" - if (PlatformDetection.IsNotHybridGlobalizationOnBrowser) - yield return new object[] { "Hello", SoftHyphen + "Hel", StringComparison.CurrentCulture, true }; + yield return new object[] { "Hello", SoftHyphen + "Hel", StringComparison.CurrentCulture, true }; } // CurrentCultureIgnoreCase @@ -4999,7 +4996,6 @@ public static IEnumerable StartsWith_StringComparison_TestData() [Theory] [MemberData(nameof(StartsWith_StringComparison_TestData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95473", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public static void StartsWith_StringComparison(string s, string value, StringComparison comparisonType, bool expected) { if (comparisonType == StringComparison.CurrentCulture) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs index 729fe7d4383b5e..e1505d67b81a84 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs @@ -1057,6 +1057,12 @@ private unsafe int IndexOf(ReadOnlySpan source, ReadOnlySpan value, { Debug.Assert(matchLengthPtr != null); *matchLengthPtr = 0; +#if TARGET_BROWSER + if (GlobalizationMode.Hybrid) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_HybridGlobalizationWithMatchLength); + } +#endif int retVal = 0; diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IndexOf.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IndexOf.cs index 2b89148d2fad20..e5c46744ad9aec 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IndexOf.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IndexOf.cs @@ -334,7 +334,22 @@ public void IndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'b', 0, CompareOptions.StringSort)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'c', 0, 2, CompareOptions.StringSort)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.StringSort)); - AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.StringSort, out _)); + if (PlatformDetection.IsHybridGlobalizationOnBrowser) + { + Assert.Throws(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.StringSort, out _)); + Assert.Throws(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out _)); + Assert.Throws(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)(-1), out _)); + Assert.Throws(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)0x11111111, out _)); + Assert.Throws(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out _)); + } + else + { + AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.StringSort, out _)); + AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out _)); + AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)(-1), out _)); + AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)0x11111111, out _)); + AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out _)); + } AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", "Tests", CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", "Tests", 0, CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); @@ -343,7 +358,6 @@ public void IndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'b', 0, CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'c', 0, 2, CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); - AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out _)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", "Tests", CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", "Tests", 0, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); @@ -352,7 +366,6 @@ public void IndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'b', 0, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'c', 0, 2, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); - AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out _)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", "Tests", (CompareOptions)(-1))); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", "Tests", 0, (CompareOptions)(-1))); @@ -361,7 +374,6 @@ public void IndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'a', 0, (CompareOptions)(-1))); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'a', 0, 2, (CompareOptions)(-1))); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)(-1))); - AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)(-1), out _)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", "Tests", (CompareOptions)0x11111111)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", "Tests", 0, (CompareOptions)0x11111111)); @@ -370,7 +382,6 @@ public void IndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'a', 0, (CompareOptions)0x11111111)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'a', 0, 2, (CompareOptions)0x11111111)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)0x11111111)); - AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)0x11111111, out _)); // StartIndex < 0 AssertExtensions.Throws("startIndex", () => s_invariantCompare.IndexOf("Test", "Test", -1, CompareOptions.None)); diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.LastIndexOf.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.LastIndexOf.cs index abb2c31f1679f7..d0ff70511b644c 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.LastIndexOf.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.LastIndexOf.cs @@ -348,7 +348,22 @@ public void LastIndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, CompareOptions.StringSort)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, CompareOptions.StringSort)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.StringSort)); - AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.StringSort, out int matchLength)); + if (PlatformDetection.IsHybridGlobalizationOnBrowser) + { + Assert.Throws(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.StringSort, out int matchLength)); + Assert.Throws(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out int matchLength)); + Assert.Throws(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out int matchLength)); + Assert.Throws(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)(-1), out int matchLength)); + Assert.Throws(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)0x11111111, out int matchLength)); + } + else + { + AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.StringSort, out int matchLength)); + AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out int matchLength)); + AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out int matchLength)); + AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)(-1), out int matchLength)); + AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)0x11111111, out int matchLength)); + } AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", 0, CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); @@ -357,7 +372,6 @@ public void LastIndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth)); - AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out int matchLength)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", 0, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); @@ -366,7 +380,6 @@ public void LastIndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth)); - AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out int matchLength)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", (CompareOptions)(-1))); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", 0, (CompareOptions)(-1))); @@ -375,7 +388,6 @@ public void LastIndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, (CompareOptions)(-1))); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, (CompareOptions)(-1))); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)(-1))); - AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)(-1), out int matchLength)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", (CompareOptions)0x11111111)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", 0, (CompareOptions)0x11111111)); @@ -384,7 +396,6 @@ public void LastIndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, (CompareOptions)0x11111111)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, (CompareOptions)0x11111111)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)0x11111111)); - AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)0x11111111, out int matchLength)); // StartIndex < 0 AssertExtensions.Throws("startIndex", () => s_invariantCompare.LastIndexOf("Test", "Test", -1, CompareOptions.None)); diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs index a6e16e2fce3caa..e30a879728cb67 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs @@ -333,7 +333,6 @@ public static IEnumerable Contains_String_StringComparison_TestData() [Theory] [MemberData(nameof(Contains_String_StringComparison_TestData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95473", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public static void Contains_String_StringComparison(string s, string value, StringComparison comparisonType, bool expected) { Assert.Equal(expected, s.Contains(value, comparisonType)); @@ -782,17 +781,21 @@ public static IEnumerable Replace_StringComparison_TestData() { yield return new object[] { "abc", "abc" + SoftHyphen, "def", StringComparison.InvariantCultureIgnoreCase, "def" }; - string turkishSource = "\u0069\u0130"; + // https://github.com/dotnet/runtime/issues/95503 + if (!PlatformDetection.IsHybridGlobalizationOnBrowser) + { + string turkishSource = "\u0069\u0130"; - yield return new object[] { turkishSource, "\u0069", "a", StringComparison.Ordinal, "a\u0130" }; - yield return new object[] { turkishSource, "\u0069", "a", StringComparison.OrdinalIgnoreCase, "a\u0130" }; - yield return new object[] { turkishSource, "\u0130", "a", StringComparison.Ordinal, "\u0069a" }; - yield return new object[] { turkishSource, "\u0130", "a", StringComparison.OrdinalIgnoreCase, "\u0069a" }; + yield return new object[] { turkishSource, "\u0069", "a", StringComparison.Ordinal, "a\u0130" }; + yield return new object[] { turkishSource, "\u0069", "a", StringComparison.OrdinalIgnoreCase, "a\u0130" }; + yield return new object[] { turkishSource, "\u0130", "a", StringComparison.Ordinal, "\u0069a" }; + yield return new object[] { turkishSource, "\u0130", "a", StringComparison.OrdinalIgnoreCase, "\u0069a" }; - yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCulture, "a\u0130" }; - yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCultureIgnoreCase, "a\u0130" }; - yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCulture, "\u0069a" }; - yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCultureIgnoreCase, "\u0069a" }; + yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCulture, "a\u0130" }; + yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCultureIgnoreCase, "a\u0130" }; + yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCulture, "\u0069a" }; + yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCultureIgnoreCase, "\u0069a" }; + } } // To catch regressions when dealing with zero-length "this" inputs @@ -801,10 +804,8 @@ public static IEnumerable Replace_StringComparison_TestData() yield return new object[] { "", "\0", "y", StringComparison.InvariantCulture, "" }; } - [Theory] - [MemberData(nameof(Replace_StringComparison_TestData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95503", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95473", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [MemberData(nameof(Replace_StringComparison_TestData))] public void Replace_StringComparison_ReturnsExpected(string original, string oldValue, string newValue, StringComparison comparisonType, string expected) { Assert.Equal(expected, original.Replace(oldValue, newValue, comparisonType)); @@ -896,7 +897,7 @@ public void Replace_StringComparison_EmptyOldValue_ThrowsArgumentException() AssertExtensions.Throws("oldValue", () => "abc".Replace("", "def", true, CultureInfo.CurrentCulture)); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] public void Replace_StringComparison_WeightlessOldValue_WithOrdinalComparison_Succeeds() { Assert.Equal("abcdef", ("abc" + ZeroWidthJoiner).Replace(ZeroWidthJoiner, "def")); @@ -904,7 +905,7 @@ public void Replace_StringComparison_WeightlessOldValue_WithOrdinalComparison_Su Assert.Equal("abcdef", ("abc" + ZeroWidthJoiner).Replace(ZeroWidthJoiner, "def", StringComparison.OrdinalIgnoreCase)); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] public void Replace_StringComparison_WeightlessOldValue_WithLinguisticComparison_TerminatesReplacement() { Assert.Equal("abc" + ZeroWidthJoiner + "def", ("abc" + ZeroWidthJoiner + "def").Replace(ZeroWidthJoiner, "xyz", StringComparison.CurrentCulture)); diff --git a/src/mono/browser/runtime/hybrid-globalization/collations.ts b/src/mono/browser/runtime/hybrid-globalization/collations.ts index 6b30ded537dfab..dd919b3c416142 100644 --- a/src/mono/browser/runtime/hybrid-globalization/collations.ts +++ b/src/mono/browser/runtime/hybrid-globalization/collations.ts @@ -86,14 +86,14 @@ export function mono_wasm_ends_with (culture: number, cultureLength: number, str export function mono_wasm_index_of (culture: number, cultureLength: number, needlePtr: number, needleLength: number, srcPtr: number, srcLength: number, options: number, fromBeginning: number, resultPtr: Int32Ptr): VoidPtr { try { - const needle = runtimeHelpers.utf16ToString(needlePtr, (needlePtr + 2 * needleLength)); + const needle = decodeToCleanStringForIndexing(needlePtr, needleLength); // no need to look for an empty string if (cleanString(needle).length == 0) { runtimeHelpers.setI32(resultPtr, fromBeginning ? 0 : srcLength); return VoidPtrNull; } - const source = runtimeHelpers.utf16ToString(srcPtr, (srcPtr + 2 * srcLength)); + const source = decodeToCleanStringForIndexing(srcPtr, srcLength); // no need to look in an empty string if (cleanString(source).length == 0) { runtimeHelpers.setI32(resultPtr, fromBeginning ? 0 : srcLength); @@ -251,5 +251,11 @@ function decodeToCleanString (strPtr: number, strLen: number) { function cleanString (str: string) { const nStr = str.normalize(); - return nStr.replace(/[\u200B-\u200D\uFEFF\0]/g, ""); + return nStr.replace(/[\u200B-\u200D\uFEFF\0\u00AD]/g, ""); +} + +// in ICU indexing only SoftHyphen is weightless +function decodeToCleanStringForIndexing (strPtr: number, strLen: number) { + const str = runtimeHelpers.utf16ToString(strPtr, (strPtr + 2 * strLen)); + return str.replace(/[\u00AD]/g, ""); }