diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json
index 0171c2942e..bf06f5bde7 100644
--- a/Jint.Tests.Test262/Test262Harness.settings.json
+++ b/Jint.Tests.Test262/Test262Harness.settings.json
@@ -12,8 +12,7 @@
"regexp-v-flag",
"source-phase-imports",
"tail-call-optimization",
- "Temporal",
- "u180e"
+ "Temporal"
],
"ExcludedFlags": [
"CanBlockIsFalse"
diff --git a/Jint/Native/String/StringPrototype.cs b/Jint/Native/String/StringPrototype.cs
index cede121225..9a830c8b0d 100644
--- a/Jint/Native/String/StringPrototype.cs
+++ b/Jint/Native/String/StringPrototype.cs
@@ -274,14 +274,132 @@ private JsValue ToLocaleLowerCase(JsValue thisObject, JsCallArguments arguments)
{
TypeConverter.RequireObjectCoercible(_engine, thisObject);
var s = TypeConverter.ToString(thisObject);
- return new JsString(s.ToLower(CultureInfo.InvariantCulture));
+ return ToLowerCaseWithSpecialCasing(s, CultureInfo.InvariantCulture);
}
private JsValue ToLowerCase(JsValue thisObject, JsCallArguments arguments)
{
TypeConverter.RequireObjectCoercible(_engine, thisObject);
var s = TypeConverter.ToString(thisObject);
- return s.ToLowerInvariant();
+ return ToLowerCaseWithSpecialCasing(s, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Converts string to lowercase with Unicode special casing rules.
+ /// Handles Final_Sigma context for Greek capital sigma (U+03A3).
+ /// https://unicode.org/reports/tr21/tr21-5.html#SpecialCasing
+ ///
+ private static string ToLowerCaseWithSpecialCasing(string s, CultureInfo culture)
+ {
+ const char GreekCapitalSigma = '\u03A3';
+
+ // Fast path: if no Greek capital sigma, use standard lowercase
+ if (s.IndexOf(GreekCapitalSigma) < 0)
+ {
+ return s.ToLower(culture);
+ }
+
+ // Need to handle Final_Sigma context
+ var result = new char[s.Length];
+ for (var i = 0; i < s.Length; i++)
+ {
+ var c = s[i];
+ if (c == GreekCapitalSigma)
+ {
+ // Check if this is a Final_Sigma context
+ // Final_Sigma: preceded by cased letter (skipping Case_Ignorable), not followed by cased letter (skipping Case_Ignorable)
+ result[i] = IsFinalSigmaContext(s, i) ? '\u03C2' : '\u03C3';
+ }
+ else
+ {
+ result[i] = char.ToLower(c, culture);
+ }
+ }
+
+ return new string(result);
+ }
+
+ ///
+ /// Determines if the character at the given position is in a Final_Sigma context.
+ /// Final_Sigma: C is preceded by a sequence consisting of a cased letter and then zero or more Case_Ignorable characters,
+ /// and C is NOT followed by a sequence consisting of zero or more Case_Ignorable characters and then a cased letter.
+ /// https://unicode.org/reports/tr21/tr21-5.html#Context
+ ///
+ private static bool IsFinalSigmaContext(string s, int index)
+ {
+ // Check backward: must find a cased letter (skipping Case_Ignorable)
+ var foundCasedBefore = false;
+ for (var i = index - 1; i >= 0; i--)
+ {
+ var c = s[i];
+ if (IsCased(c))
+ {
+ foundCasedBefore = true;
+ break;
+ }
+ if (!IsCaseIgnorable(c))
+ {
+ break;
+ }
+ }
+
+ if (!foundCasedBefore)
+ {
+ return false;
+ }
+
+ // Check forward: must NOT find a cased letter (skipping Case_Ignorable)
+ for (var i = index + 1; i < s.Length; i++)
+ {
+ var c = s[i];
+ if (IsCased(c))
+ {
+ return false; // Found cased letter after, so NOT Final_Sigma
+ }
+ if (!IsCaseIgnorable(c))
+ {
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Checks if a character is "cased" (has uppercase or lowercase property).
+ /// A character is cased if it has the Lowercase or Uppercase property, or has General_Category=Titlecase_Letter.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsCased(char c)
+ {
+ // Cased = Lowercase OR Uppercase OR General_Category=Lt
+ return char.IsLetter(c) && (char.IsLower(c) || char.IsUpper(c) || CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.TitlecaseLetter);
+ }
+
+ ///
+ /// Checks if a character is Case_Ignorable.
+ /// Case_Ignorable characters include: Mn (Nonspacing_Mark), Me (Enclosing_Mark), Cf (Format),
+ /// Lm (Modifier_Letter), Sk (Modifier_Symbol), and characters with Word_Break property MidLetter, MidNumLet, or Single_Quote.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsCaseIgnorable(char c)
+ {
+ var category = CharUnicodeInfo.GetUnicodeCategory(c);
+ return category == UnicodeCategory.NonSpacingMark || // Mn
+ category == UnicodeCategory.EnclosingMark || // Me
+ category == UnicodeCategory.Format || // Cf (includes U+180E Mongolian Vowel Separator)
+ category == UnicodeCategory.ModifierLetter || // Lm
+ category == UnicodeCategory.ModifierSymbol || // Sk
+ c == '\u0027' || // APOSTROPHE (Word_Break=Single_Quote)
+ c == '\u00B7' || // MIDDLE DOT (Word_Break=MidLetter)
+ c == '\u0387' || // GREEK ANO TELEIA (Word_Break=MidLetter)
+ c == '\u05F4' || // HEBREW PUNCTUATION GERSHAYIM (Word_Break=MidLetter)
+ c == '\u2019' || // RIGHT SINGLE QUOTATION MARK (Word_Break=Single_Quote)
+ c == '\u2027' || // HYPHENATION POINT (Word_Break=MidLetter)
+ c == '\uFE13' || // PRESENTATION FORM FOR VERTICAL COLON (Word_Break=MidLetter)
+ c == '\uFE55' || // SMALL COLON (Word_Break=MidLetter)
+ c == '\uFF07' || // FULLWIDTH APOSTROPHE (Word_Break=MidNumLet)
+ c == '\uFF1A'; // FULLWIDTH COLON (Word_Break=MidLetter)
}
private static int ToIntegerSupportInfinity(JsValue numberVal)