Skip to content

Commit a842025

Browse files
authoredAug 15, 2024
[browser] Fix HybridGlobalization, SoftHyphen is not ignored when it should be (dotnet#105946)
* Ignore SoftHyphen. * Update docs. * Implementation with `matchLength` should throw with PNSE, not ArgumentException. * String.Replace uses IndexOf with matchLength, so expect it to throw.

File tree

7 files changed

+81
-37
lines changed

7 files changed

+81
-37
lines changed
 

‎docs/design/features/globalization-hybrid-mode.md

+16-3
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,12 @@ Dependencies:
270270

271271
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:
272272

273-
- [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@))
274-
- [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@))
273+
- [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@))
274+
- [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@))
275275

276276
- `IgnoreSymbols`
277277
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`.
278278

279-
280279
**String indexing**
281280

282281
Affected public APIs:
@@ -287,6 +286,15 @@ Affected public APIs:
287286

288287
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:
289288

289+
- Methods that calculate `matchLength` always return throw PlatformNotSupported exception:
290+
291+
[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@))
292+
293+
[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@))
294+
295+
- String.Replace that uses `StringComparison` argument relies internally on IndexOf with `matchLength` argument. From this reason, it throws PlatformNotSupportedException:
296+
[String.Replace](https://learn.microsoft.com/en-us/dotnet/api/system.string.replace?view=system-string-replace(system-string-system-string-system-stringcomparison))
297+
290298
- Support depends on [`Intl.segmenter's support`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter#browser_compatibility).
291299

292300
- `IgnoreSymbols`
@@ -463,6 +471,11 @@ Affected public APIs:
463471
- String.IndexOf
464472
- String.LastIndexOf
465473

474+
Methods that calculate `matchLength` throw PlatformNotSupported exception:
475+
[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@))
476+
477+
[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@))
478+
466479
Mapped to Apple Native API `rangeOfString:options:range:locale:`(https://developer.apple.com/documentation/foundation/nsstring/1417348-rangeofstring?language=objc)
467480

468481
In `rangeOfString:options:range:locale:` objects are compared by checking the Unicode canonical equivalence of their code point sequences.

‎src/libraries/Common/tests/Tests/System/StringTests.cs

+1-5
Original file line numberDiff line numberDiff line change
@@ -1730,7 +1730,6 @@ public static IEnumerable<object[]> EndsWith_StringComparison_TestData()
17301730

17311731
[Theory]
17321732
[MemberData(nameof(EndsWith_StringComparison_TestData))]
1733-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95473", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))]
17341733
public static void EndsWith_StringComparison(string s, string value, StringComparison comparisonType, bool expected)
17351734
{
17361735
if (comparisonType == StringComparison.CurrentCulture)
@@ -4925,9 +4924,7 @@ public static IEnumerable<object[]> StartsWith_StringComparison_TestData()
49254924

49264925
if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform)
49274926
{
4928-
// "https://github.com/dotnet/runtime/issues/95473"
4929-
if (PlatformDetection.IsNotHybridGlobalizationOnBrowser)
4930-
yield return new object[] { "Hello", SoftHyphen + "Hel", StringComparison.CurrentCulture, true };
4927+
yield return new object[] { "Hello", SoftHyphen + "Hel", StringComparison.CurrentCulture, true };
49314928
}
49324929

49334930
// CurrentCultureIgnoreCase
@@ -4999,7 +4996,6 @@ public static IEnumerable<object[]> StartsWith_StringComparison_TestData()
49994996

50004997
[Theory]
50014998
[MemberData(nameof(StartsWith_StringComparison_TestData))]
5002-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95473", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))]
50034999
public static void StartsWith_StringComparison(string s, string value, StringComparison comparisonType, bool expected)
50045000
{
50055001
if (comparisonType == StringComparison.CurrentCulture)

‎src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs

+6
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,12 @@ private unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value,
10571057
{
10581058
Debug.Assert(matchLengthPtr != null);
10591059
*matchLengthPtr = 0;
1060+
#if TARGET_BROWSER
1061+
if (GlobalizationMode.Hybrid)
1062+
{
1063+
throw new PlatformNotSupportedException(SR.PlatformNotSupported_HybridGlobalizationWithMatchLength);
1064+
}
1065+
#endif
10601066

10611067
int retVal = 0;
10621068

‎src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IndexOf.cs

+16-5
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,22 @@ public void IndexOf_Invalid()
334334
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'b', 0, CompareOptions.StringSort));
335335
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'c', 0, 2, CompareOptions.StringSort));
336336
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.StringSort));
337-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.StringSort, out _));
337+
if (PlatformDetection.IsHybridGlobalizationOnBrowser)
338+
{
339+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.StringSort, out _));
340+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out _));
341+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)(-1), out _));
342+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)0x11111111, out _));
343+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out _));
344+
}
345+
else
346+
{
347+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.StringSort, out _));
348+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out _));
349+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)(-1), out _));
350+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)0x11111111, out _));
351+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out _));
352+
}
338353

339354
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", "Tests", CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
340355
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", "Tests", 0, CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
@@ -343,7 +358,6 @@ public void IndexOf_Invalid()
343358
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'b', 0, CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
344359
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'c', 0, 2, CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
345360
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
346-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out _));
347361

348362
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", "Tests", CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
349363
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", "Tests", 0, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
@@ -352,7 +366,6 @@ public void IndexOf_Invalid()
352366
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'b', 0, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
353367
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'c', 0, 2, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
354368
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
355-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out _));
356369

357370
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", "Tests", (CompareOptions)(-1)));
358371
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", "Tests", 0, (CompareOptions)(-1)));
@@ -361,7 +374,6 @@ public void IndexOf_Invalid()
361374
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'a', 0, (CompareOptions)(-1)));
362375
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'a', 0, 2, (CompareOptions)(-1)));
363376
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)(-1)));
364-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)(-1), out _));
365377

366378
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", "Tests", (CompareOptions)0x11111111));
367379
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", "Tests", 0, (CompareOptions)0x11111111));
@@ -370,7 +382,6 @@ public void IndexOf_Invalid()
370382
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'a', 0, (CompareOptions)0x11111111));
371383
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's", 'a', 0, 2, (CompareOptions)0x11111111));
372384
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)0x11111111));
373-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), (CompareOptions)0x11111111, out _));
374385

375386
// StartIndex < 0
376387
AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => s_invariantCompare.IndexOf("Test", "Test", -1, CompareOptions.None));

‎src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.LastIndexOf.cs

+16-5
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,22 @@ public void LastIndexOf_Invalid()
348348
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, CompareOptions.StringSort));
349349
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, CompareOptions.StringSort));
350350
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.StringSort));
351-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.StringSort, out int matchLength));
351+
if (PlatformDetection.IsHybridGlobalizationOnBrowser)
352+
{
353+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.StringSort, out int matchLength));
354+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out int matchLength));
355+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out int matchLength));
356+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)(-1), out int matchLength));
357+
Assert.Throws<PlatformNotSupportedException>(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)0x11111111, out int matchLength));
358+
}
359+
else
360+
{
361+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.StringSort, out int matchLength));
362+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out int matchLength));
363+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out int matchLength));
364+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)(-1), out int matchLength));
365+
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)0x11111111, out int matchLength));
366+
}
352367

353368
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
354369
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", 0, CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
@@ -357,7 +372,6 @@ public void LastIndexOf_Invalid()
357372
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
358373
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
359374
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth));
360-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.Ordinal | CompareOptions.IgnoreWidth, out int matchLength));
361375

362376
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
363377
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", 0, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
@@ -366,7 +380,6 @@ public void LastIndexOf_Invalid()
366380
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
367381
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
368382
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth));
369-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreWidth, out int matchLength));
370383

371384
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", (CompareOptions)(-1)));
372385
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", 0, (CompareOptions)(-1)));
@@ -375,7 +388,6 @@ public void LastIndexOf_Invalid()
375388
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, (CompareOptions)(-1)));
376389
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, (CompareOptions)(-1)));
377390
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)(-1)));
378-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)(-1), out int matchLength));
379391

380392
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", (CompareOptions)0x11111111));
381393
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "Tests", 0, (CompareOptions)0x11111111));
@@ -384,7 +396,6 @@ public void LastIndexOf_Invalid()
384396
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, (CompareOptions)0x11111111));
385397
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, (CompareOptions)0x11111111));
386398
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)0x11111111));
387-
AssertExtensions.Throws<ArgumentException>("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), (CompareOptions)0x11111111, out int matchLength));
388399

389400
// StartIndex < 0
390401
AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => s_invariantCompare.LastIndexOf("Test", "Test", -1, CompareOptions.None));

‎src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs

+17-16
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,6 @@ public static IEnumerable<object[]> Contains_String_StringComparison_TestData()
333333

334334
[Theory]
335335
[MemberData(nameof(Contains_String_StringComparison_TestData))]
336-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95473", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))]
337336
public static void Contains_String_StringComparison(string s, string value, StringComparison comparisonType, bool expected)
338337
{
339338
Assert.Equal(expected, s.Contains(value, comparisonType));
@@ -782,17 +781,21 @@ public static IEnumerable<object[]> Replace_StringComparison_TestData()
782781
{
783782
yield return new object[] { "abc", "abc" + SoftHyphen, "def", StringComparison.InvariantCultureIgnoreCase, "def" };
784783

785-
string turkishSource = "\u0069\u0130";
784+
// https://github.com/dotnet/runtime/issues/95503
785+
if (!PlatformDetection.IsHybridGlobalizationOnBrowser)
786+
{
787+
string turkishSource = "\u0069\u0130";
786788

787-
yield return new object[] { turkishSource, "\u0069", "a", StringComparison.Ordinal, "a\u0130" };
788-
yield return new object[] { turkishSource, "\u0069", "a", StringComparison.OrdinalIgnoreCase, "a\u0130" };
789-
yield return new object[] { turkishSource, "\u0130", "a", StringComparison.Ordinal, "\u0069a" };
790-
yield return new object[] { turkishSource, "\u0130", "a", StringComparison.OrdinalIgnoreCase, "\u0069a" };
789+
yield return new object[] { turkishSource, "\u0069", "a", StringComparison.Ordinal, "a\u0130" };
790+
yield return new object[] { turkishSource, "\u0069", "a", StringComparison.OrdinalIgnoreCase, "a\u0130" };
791+
yield return new object[] { turkishSource, "\u0130", "a", StringComparison.Ordinal, "\u0069a" };
792+
yield return new object[] { turkishSource, "\u0130", "a", StringComparison.OrdinalIgnoreCase, "\u0069a" };
791793

792-
yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCulture, "a\u0130" };
793-
yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCultureIgnoreCase, "a\u0130" };
794-
yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCulture, "\u0069a" };
795-
yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCultureIgnoreCase, "\u0069a" };
794+
yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCulture, "a\u0130" };
795+
yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCultureIgnoreCase, "a\u0130" };
796+
yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCulture, "\u0069a" };
797+
yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCultureIgnoreCase, "\u0069a" };
798+
}
796799
}
797800

798801
// To catch regressions when dealing with zero-length "this" inputs
@@ -801,10 +804,8 @@ public static IEnumerable<object[]> Replace_StringComparison_TestData()
801804
yield return new object[] { "", "\0", "y", StringComparison.InvariantCulture, "" };
802805
}
803806

804-
[Theory]
805-
[MemberData(nameof(Replace_StringComparison_TestData))]
806-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95503", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))]
807-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95473", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))]
807+
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))]
808+
[MemberData(nameof(Replace_StringComparison_TestData))]
808809
public void Replace_StringComparison_ReturnsExpected(string original, string oldValue, string newValue, StringComparison comparisonType, string expected)
809810
{
810811
Assert.Equal(expected, original.Replace(oldValue, newValue, comparisonType));
@@ -896,15 +897,15 @@ public void Replace_StringComparison_EmptyOldValue_ThrowsArgumentException()
896897
AssertExtensions.Throws<ArgumentException>("oldValue", () => "abc".Replace("", "def", true, CultureInfo.CurrentCulture));
897898
}
898899

899-
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))]
900+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))]
900901
public void Replace_StringComparison_WeightlessOldValue_WithOrdinalComparison_Succeeds()
901902
{
902903
Assert.Equal("abcdef", ("abc" + ZeroWidthJoiner).Replace(ZeroWidthJoiner, "def"));
903904
Assert.Equal("abcdef", ("abc" + ZeroWidthJoiner).Replace(ZeroWidthJoiner, "def", StringComparison.Ordinal));
904905
Assert.Equal("abcdef", ("abc" + ZeroWidthJoiner).Replace(ZeroWidthJoiner, "def", StringComparison.OrdinalIgnoreCase));
905906
}
906907

907-
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))]
908+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))]
908909
public void Replace_StringComparison_WeightlessOldValue_WithLinguisticComparison_TerminatesReplacement()
909910
{
910911
Assert.Equal("abc" + ZeroWidthJoiner + "def", ("abc" + ZeroWidthJoiner + "def").Replace(ZeroWidthJoiner, "xyz", StringComparison.CurrentCulture));

‎src/mono/browser/runtime/hybrid-globalization/collations.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,14 @@ export function mono_wasm_ends_with (culture: number, cultureLength: number, str
8686

8787
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 {
8888
try {
89-
const needle = runtimeHelpers.utf16ToString(<any>needlePtr, <any>(needlePtr + 2 * needleLength));
89+
const needle = decodeToCleanStringForIndexing(needlePtr, needleLength);
9090
// no need to look for an empty string
9191
if (cleanString(needle).length == 0) {
9292
runtimeHelpers.setI32(resultPtr, fromBeginning ? 0 : srcLength);
9393
return VoidPtrNull;
9494
}
9595

96-
const source = runtimeHelpers.utf16ToString(<any>srcPtr, <any>(srcPtr + 2 * srcLength));
96+
const source = decodeToCleanStringForIndexing(srcPtr, srcLength);
9797
// no need to look in an empty string
9898
if (cleanString(source).length == 0) {
9999
runtimeHelpers.setI32(resultPtr, fromBeginning ? 0 : srcLength);
@@ -251,5 +251,11 @@ function decodeToCleanString (strPtr: number, strLen: number) {
251251

252252
function cleanString (str: string) {
253253
const nStr = str.normalize();
254-
return nStr.replace(/[\u200B-\u200D\uFEFF\0]/g, "");
254+
return nStr.replace(/[\u200B-\u200D\uFEFF\0\u00AD]/g, "");
255+
}
256+
257+
// in ICU indexing only SoftHyphen is weightless
258+
function decodeToCleanStringForIndexing (strPtr: number, strLen: number) {
259+
const str = runtimeHelpers.utf16ToString(<any>strPtr, <any>(strPtr + 2 * strLen));
260+
return str.replace(/[\u00AD]/g, "");
255261
}

0 commit comments

Comments
 (0)
Please sign in to comment.