Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 4 additions & 32 deletions Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,8 @@
// Many of these tests require non-English locales (Spanish, Polish, German, Chinese, etc.) or
// advanced features like formatRange, calendar-specific formatting, or complex locale canonicalization.

// Collator - German phonebook collation and locale-specific collation data
// Collator - German phonebook collation
"intl402/Collator/prototype/compare/non-normative-phonebook.js",
"intl402/Collator/prototype/resolvedOptions/resolved-collation-unicode-extensions-and-options.js",
"intl402/Collator/unicode-ext-seq-in-private-tag.js",
"intl402/Collator/unicode-ext-value-collation.js",
"intl402/Collator/usage-de.js",

// DateTimeFormat - hanidec numbering system has different AM/PM behavior in ICU/CLDR
Expand All @@ -132,22 +129,10 @@
"intl402/DateTimeFormat/prototype/formatRangeToParts/dangi-calendar-dates.js",
"intl402/DateTimeFormat/prototype/formatToParts/chinese-calendar-dates.js",

// DisplayNames - locale-specific display names and Symbol edge cases
"intl402/DisplayNames/locales-symbol-length.js",

// DurationFormat - formatting with specific styles and locales
"intl402/DurationFormat/prototype/format/duration-out-of-range-3.js",
"intl402/DurationFormat/prototype/format/duration-out-of-range-4.js",
// DurationFormat - precision formatting requires exact sub-second arithmetic
"intl402/DurationFormat/prototype/format/precision-exact-mathematical-values.js",
// Requires Spanish unit patterns (ICU4N doesn't include unit data, only core locale data)
"intl402/DurationFormat/prototype/format/mixed-non-numeric-styles-es.js",
"intl402/DurationFormat/prototype/format/precision-exact-mathematical-values.js",
"intl402/DurationFormat/prototype/resolvedOptions/resolved-numbering-system-unicode-extensions-and-options.js",

// Locale canonicalization - variant/region aliasing requires CLDR data
"intl402/Intl/getCanonicalLocales/complex-region-subtag-replacement.js",
"intl402/Intl/getCanonicalLocales/non-iana-canon.js",
"intl402/Intl/getCanonicalLocales/preferred-variant.js",
"intl402/Locale/reject-duplicate-variants-in-tlang.js",

// NumberFormat - locale-specific formatting and formatRange
"intl402/NumberFormat/prototype/format/format-significant-digits.js",
Expand All @@ -163,28 +148,15 @@
"intl402/NumberFormat/prototype/formatToParts/unit-ko-KR.js",
"intl402/NumberFormat/prototype/formatToParts/unit-zh-TW.js",

// PluralRules - plural categories require full CLDR data, notation requires compact support
"intl402/PluralRules/prototype/resolvedOptions/plural-categories-order.js",
// PluralRules - notation requires compact support
"intl402/PluralRules/prototype/select/notation.js",

// String - Turkish/Lithuanian/Azeri locale-specific case conversions
"intl402/String/prototype/toLocaleLowerCase/capital_I_with_dot.js",
"intl402/String/prototype/toLocaleLowerCase/special_casing_Azeri.js",
"intl402/String/prototype/toLocaleLowerCase/special_casing_Lithuanian.js",
"intl402/String/prototype/toLocaleLowerCase/special_casing_Turkish.js",
"intl402/String/prototype/toLocaleLowerCase/validates-all-locale-identifiers.js",
"intl402/String/prototype/toLocaleUpperCase/validates-all-locale-identifiers.js",

// supportedValuesOf - currencies accepted by DisplayNames check
"intl402/Intl/supportedValuesOf/currencies-accepted-by-DisplayNames.js",

// Misc intl402 tests requiring full locale support
"intl402/fallback-locales-are-supported.js",
"intl402/supportedLocalesOf-consistent-with-resolvedOptions.js",

// misc features that require investigation
"intl402/NumberFormat/prototype/format/numbering-systems.js",
"intl402/Intl/supportedValuesOf/numberingSystems-with-simple-digit-mappings.js",

// Temporal
"intl402/Temporal/Duration/**/*.js",
Expand Down
2 changes: 1 addition & 1 deletion Jint/Native/BigInt/BigIntConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private JsBigInt NumberToBigInt(JsNumber value)
{
if (TypeConverter.IsIntegralNumber(value._value))
{
return JsBigInt.Create((long) value._value);
return JsBigInt.Create(new BigInteger(value._value));
}

Throw.RangeError(_realm, "The number " + value + " cannot be converted to a BigInt because it is not an integer");
Expand Down
72 changes: 59 additions & 13 deletions Jint/Native/Intl/CollatorConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ internal sealed class CollatorConstructor : Constructor
"searchjl", "stroke", "trad", "unihan", "zhuyin", "default"
], StringComparison.Ordinal);

// Locale-specific supported collation types (from CLDR)
// Only locales with non-default collation support are listed
private static readonly Dictionary<string, HashSet<string>> LocaleCollationSupport = new(StringComparer.OrdinalIgnoreCase)
{
["ar"] = new(StringComparer.Ordinal) { "default", "compat", "eor" },
["da"] = new(StringComparer.Ordinal) { "default", "eor" },
["de"] = new(StringComparer.Ordinal) { "default", "phonebk", "eor" },
["en"] = new(StringComparer.Ordinal) { "default", "ducet", "emoji", "eor" },
["es"] = new(StringComparer.Ordinal) { "default", "trad", "eor" },
["hi"] = new(StringComparer.Ordinal) { "default", "direct", "eor" },
["ja"] = new(StringComparer.Ordinal) { "default", "unihan", "eor" },
["ko"] = new(StringComparer.Ordinal) { "default", "searchjl", "unihan", "eor" },
["ln"] = new(StringComparer.Ordinal) { "default", "phonetic", "eor" },
["si"] = new(StringComparer.Ordinal) { "default", "dict", "eor" },
["sv"] = new(StringComparer.Ordinal) { "default", "reformed", "eor" },
["zh"] = new(StringComparer.Ordinal) { "default", "big5han", "gb2312", "pinyin", "stroke", "unihan", "zhuyin", "eor" },
};

public CollatorConstructor(
Engine engine,
Realm realm,
Expand Down Expand Up @@ -90,7 +108,7 @@ public override ObjectInstance Construct(JsCallArguments arguments, JsValue newT
// Get options (options override unicode extensions)
var usage = GetStringOption(optionsObj, "usage", UsageValues, "sort");
var sensitivity = GetSensitivity(optionsObj);
var collation = GetCollationOption(optionsObj, uCollation);
var collation = GetCollationOption(optionsObj, uCollation, resolvedLocale);
var numeric = GetNumericOption(optionsObj, uNumeric);
var caseFirst = GetCaseFirstOption(optionsObj, uCaseFirst);

Expand Down Expand Up @@ -224,13 +242,18 @@ private static void ParseUnicodeExtensions(string locale, out string? collation,
numeric = null;
caseFirst = null;

var uIndex = locale.IndexOf("-u-", StringComparison.Ordinal);
// Only search for -u- before the private-use section (-x-)
var xIndex = locale.IndexOf("-x-", StringComparison.OrdinalIgnoreCase);
var searchRange = xIndex >= 0 ? locale.Substring(0, xIndex) : locale;
var uIndex = searchRange.IndexOf("-u-", StringComparison.Ordinal);
if (uIndex < 0)
{
return;
}

var parts = locale.Substring(uIndex + 3).Split('-');
// Extract the -u- extension content (up to private-use or next singleton)
var extensionContent = (xIndex >= 0 ? locale.Substring(uIndex + 3, xIndex - uIndex - 3) : locale.Substring(uIndex + 3));
var parts = extensionContent.Split('-');
for (var i = 0; i < parts.Length; i++)
{
var key = parts[i];
Expand Down Expand Up @@ -270,42 +293,65 @@ private static void ParseUnicodeExtensions(string locale, out string? collation,
}
}

private static string GetCollationOption(ObjectInstance options, string? unicodeExtension)
private static string GetCollationOption(ObjectInstance options, string? unicodeExtension, string resolvedLocale)
{
// Get the language code for locale-specific collation support
var langCode = resolvedLocale;
var dashIdx = resolvedLocale.IndexOf('-');
if (dashIdx > 0)
{
langCode = resolvedLocale.Substring(0, dashIdx);
}

var value = options.Get("collation");
if (!value.IsUndefined())
{
var collation = TypeConverter.ToString(value);

// Per ECMA-402: "standard" and "search" collations are explicitly disallowed
// They should fall back to "default"
if (string.Equals(collation, "standard", StringComparison.Ordinal) ||
string.Equals(collation, "search", StringComparison.Ordinal))
{
return "default";
// Fall through to check unicode extension
}

// Validate against known collation types - ignore invalid values
if (!ValidCollationTypes.Contains(collation))
// Validate against known collation types AND locale-specific support
else if (ValidCollationTypes.Contains(collation) && IsCollationSupportedForLocale(langCode, collation))
{
return "default";
return collation;
}

return collation;
// Options value is not supported - fall through to check unicode extension
}

// Check unicode extension, but disallow "standard", "search", and invalid values
if (unicodeExtension != null &&
!string.Equals(unicodeExtension, "standard", StringComparison.Ordinal) &&
!string.Equals(unicodeExtension, "search", StringComparison.Ordinal) &&
ValidCollationTypes.Contains(unicodeExtension))
ValidCollationTypes.Contains(unicodeExtension) &&
IsCollationSupportedForLocale(langCode, unicodeExtension))
{
return unicodeExtension;
}

return "default";
}

private static bool IsCollationSupportedForLocale(string language, string collation)
{
if (string.Equals(collation, "default", StringComparison.Ordinal))
{
return true;
}

// If we have explicit locale data, check against it
if (LocaleCollationSupport.TryGetValue(language, out var supported))
{
return supported.Contains(collation);
}

// For unlisted locales, only "default" and "eor" are universally supported
return string.Equals(collation, "eor", StringComparison.Ordinal);
}

private static bool GetNumericOption(ObjectInstance options, bool? unicodeExtension)
{
var value = options.Get("numeric");
Expand Down
28 changes: 20 additions & 8 deletions Jint/Native/Intl/Data/LocaleData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,20 @@ private static void ParseComplexLanguageMapping(string key, string value)

private static void ParseVariantMapping(string key, string value)
{
// Format: type,replacement
var commaIndex = value.IndexOf(',');
if (commaIndex > 0)
// Format: type,replacement[,prefix:variant]
var parts = value.Split(',');
if (parts.Length >= 2)
{
_variantMappings![key] = new VariantMapping(
value.Substring(0, commaIndex),
value.Substring(commaIndex + 1)
);
string? prefix = null;
for (var i = 2; i < parts.Length; i++)
{
if (parts[i].StartsWith("prefix:", StringComparison.Ordinal))
{
prefix = parts[i].Substring(7);
}
}

_variantMappings![key] = new VariantMapping(parts[0], parts[1], prefix);
}
}

Expand Down Expand Up @@ -284,13 +290,19 @@ public ComplexLanguageMapping(string language, string? script, string? region)

internal readonly struct VariantMapping
{
public VariantMapping(string type, string replacement)
public VariantMapping(string type, string replacement, string? prefix = null)
{
Type = type;
Replacement = replacement;
Prefix = prefix;
}

public string Type { get; }
public string Replacement { get; }

/// <summary>
/// Optional prefix variant that should be removed when this alias is applied.
/// </summary>
public string? Prefix { get; }
}
}
13 changes: 12 additions & 1 deletion Jint/Native/Intl/Data/LocaleData.txt
Original file line number Diff line number Diff line change
Expand Up @@ -694,12 +694,16 @@ WK=UM
YD=YE
YU=RS
ZR=CD
810=RU
NT=SA
SU=RU
AN=CW

[VARIANT_MAPPINGS]
aaland=region,AX
arevela=language,hy
arevmda=language,hyw
heploc=variant,alalc97
heploc=variant,alalc97,prefix:hepburn
polytoni=variant,polyton

[LANGUAGE_VARIANT_MAPPINGS]
Expand All @@ -715,6 +719,13 @@ Cyrl+SU=RU
Latn+SU=RU
Geor+SU=GE
Arab+SU=UZ
Armn+810=AM
Cyrl+810=RU
Latn+810=RU
Geor+810=GE
Arab+810=UZ
Cyrl+CS=RS
Latn+CS=RS

[UNICODE_MAPPINGS]
ca:ethiopic-amete-alem=ethioaa
Expand Down
9 changes: 9 additions & 0 deletions Jint/Native/Intl/Data/NumberingSystemData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ internal static class NumberingSystemData
["deva"] = "\u0966\u0967\u0968\u0969\u096A\u096B\u096C\u096D\u096E\u096F",
["diak"] = "\U00011950\U00011951\U00011952\U00011953\U00011954\U00011955\U00011956\U00011957\U00011958\U00011959",
["fullwide"] = "\uFF10\uFF11\uFF12\uFF13\uFF14\uFF15\uFF16\uFF17\uFF18\uFF19",
["gara"] = "\U00010D40\U00010D41\U00010D42\U00010D43\U00010D44\U00010D45\U00010D46\U00010D47\U00010D48\U00010D49",
["gong"] = "\U00011DA0\U00011DA1\U00011DA2\U00011DA3\U00011DA4\U00011DA5\U00011DA6\U00011DA7\U00011DA8\U00011DA9",
["gonm"] = "\U00011D50\U00011D51\U00011D52\U00011D53\U00011D54\U00011D55\U00011D56\U00011D57\U00011D58\U00011D59",
["gujr"] = "\u0AE6\u0AE7\u0AE8\u0AE9\u0AEA\u0AEB\u0AEC\u0AED\u0AEE\u0AEF",
["gukh"] = "\U00016130\U00016131\U00016132\U00016133\U00016134\U00016135\U00016136\U00016137\U00016138\U00016139",
["guru"] = "\u0A66\u0A67\u0A68\u0A69\u0A6A\u0A6B\u0A6C\u0A6D\u0A6E\u0A6F",
["hanidec"] = "\u3007\u4E00\u4E8C\u4E09\u56DB\u4E94\u516D\u4E03\u516B\u4E5D",
["hmng"] = "\U00016B50\U00016B51\U00016B52\U00016B53\U00016B54\U00016B55\U00016B56\U00016B57\U00016B58\U00016B59",
Expand All @@ -36,6 +38,7 @@ internal static class NumberingSystemData
["kawi"] = "\U00011F50\U00011F51\U00011F52\U00011F53\U00011F54\U00011F55\U00011F56\U00011F57\U00011F58\U00011F59",
["khmr"] = "\u17E0\u17E1\u17E2\u17E3\u17E4\u17E5\u17E6\u17E7\u17E8\u17E9",
["knda"] = "\u0CE6\u0CE7\u0CE8\u0CE9\u0CEA\u0CEB\u0CEC\u0CED\u0CEE\u0CEF",
["krai"] = "\U00016D70\U00016D71\U00016D72\U00016D73\U00016D74\U00016D75\U00016D76\U00016D77\U00016D78\U00016D79",
["lana"] = "\u1A80\u1A81\u1A82\u1A83\u1A84\u1A85\u1A86\u1A87\u1A88\u1A89",
["lanatham"] = "\u1A90\u1A91\u1A92\u1A93\u1A94\u1A95\u1A96\u1A97\u1A98\u1A99",
["laoo"] = "\u0ED0\u0ED1\u0ED2\u0ED3\u0ED4\u0ED5\u0ED6\u0ED7\u0ED8\u0ED9",
Expand All @@ -53,14 +56,18 @@ internal static class NumberingSystemData
["mroo"] = "\U00016A60\U00016A61\U00016A62\U00016A63\U00016A64\U00016A65\U00016A66\U00016A67\U00016A68\U00016A69",
["mtei"] = "\uABF0\uABF1\uABF2\uABF3\uABF4\uABF5\uABF6\uABF7\uABF8\uABF9",
["mymr"] = "\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049",
["mymrepka"] = "\U000116DA\U000116DB\U000116DC\U000116DD\U000116DE\U000116DF\U000116E0\U000116E1\U000116E2\U000116E3",
["mymrpao"] = "\U000116D0\U000116D1\U000116D2\U000116D3\U000116D4\U000116D5\U000116D6\U000116D7\U000116D8\U000116D9",
["mymrshan"] = "\u1090\u1091\u1092\u1093\u1094\u1095\u1096\u1097\u1098\u1099",
["mymrtlng"] = "\uA9F0\uA9F1\uA9F2\uA9F3\uA9F4\uA9F5\uA9F6\uA9F7\uA9F8\uA9F9",
["nagm"] = "\U0001E4F0\U0001E4F1\U0001E4F2\U0001E4F3\U0001E4F4\U0001E4F5\U0001E4F6\U0001E4F7\U0001E4F8\U0001E4F9",
["newa"] = "\U00011450\U00011451\U00011452\U00011453\U00011454\U00011455\U00011456\U00011457\U00011458\U00011459",
["nkoo"] = "\u07C0\u07C1\u07C2\u07C3\u07C4\u07C5\u07C6\u07C7\u07C8\u07C9",
["olck"] = "\u1C50\u1C51\u1C52\u1C53\u1C54\u1C55\u1C56\u1C57\u1C58\u1C59",
["onao"] = "\U0001E5F1\U0001E5F2\U0001E5F3\U0001E5F4\U0001E5F5\U0001E5F6\U0001E5F7\U0001E5F8\U0001E5F9\U0001E5FA",
["orya"] = "\u0B66\u0B67\u0B68\u0B69\u0B6A\u0B6B\u0B6C\u0B6D\u0B6E\u0B6F",
["osma"] = "\U000104A0\U000104A1\U000104A2\U000104A3\U000104A4\U000104A5\U000104A6\U000104A7\U000104A8\U000104A9",
["outlined"] = "\U0001CCF0\U0001CCF1\U0001CCF2\U0001CCF3\U0001CCF4\U0001CCF5\U0001CCF6\U0001CCF7\U0001CCF8\U0001CCF9",
["rohg"] = "\U00010D30\U00010D31\U00010D32\U00010D33\U00010D34\U00010D35\U00010D36\U00010D37\U00010D38\U00010D39",
["saur"] = "\uA8D0\uA8D1\uA8D2\uA8D3\uA8D4\uA8D5\uA8D6\uA8D7\uA8D8\uA8D9",
["segment"] = "\U0001FBF0\U0001FBF1\U0001FBF2\U0001FBF3\U0001FBF4\U0001FBF5\U0001FBF6\U0001FBF7\U0001FBF8\U0001FBF9",
Expand All @@ -69,6 +76,7 @@ internal static class NumberingSystemData
["sinh"] = "\u0DE6\u0DE7\u0DE8\u0DE9\u0DEA\u0DEB\u0DEC\u0DED\u0DEE\u0DEF",
["sora"] = "\U000110F0\U000110F1\U000110F2\U000110F3\U000110F4\U000110F5\U000110F6\U000110F7\U000110F8\U000110F9",
["sund"] = "\u1BB0\u1BB1\u1BB2\u1BB3\u1BB4\u1BB5\u1BB6\u1BB7\u1BB8\u1BB9",
["sunu"] = "\U00011BF0\U00011BF1\U00011BF2\U00011BF3\U00011BF4\U00011BF5\U00011BF6\U00011BF7\U00011BF8\U00011BF9",
["takr"] = "\U000116C0\U000116C1\U000116C2\U000116C3\U000116C4\U000116C5\U000116C6\U000116C7\U000116C8\U000116C9",
["talu"] = "\u19D0\u19D1\u19D2\u19D3\u19D4\u19D5\u19D6\u19D7\u19D8\u19D9",
["tamldec"] = "\u0BE6\u0BE7\u0BE8\u0BE9\u0BEA\u0BEB\u0BEC\u0BED\u0BEE\u0BEF",
Expand All @@ -77,6 +85,7 @@ internal static class NumberingSystemData
["tibt"] = "\u0F20\u0F21\u0F22\u0F23\u0F24\u0F25\u0F26\u0F27\u0F28\u0F29",
["tirh"] = "\U000114D0\U000114D1\U000114D2\U000114D3\U000114D4\U000114D5\U000114D6\U000114D7\U000114D8\U000114D9",
["tnsa"] = "\U00016AC0\U00016AC1\U00016AC2\U00016AC3\U00016AC4\U00016AC5\U00016AC6\U00016AC7\U00016AC8\U00016AC9",
["tols"] = "\U00011DE0\U00011DE1\U00011DE2\U00011DE3\U00011DE4\U00011DE5\U00011DE6\U00011DE7\U00011DE8\U00011DE9",
["vaii"] = "\uA620\uA621\uA622\uA623\uA624\uA625\uA626\uA627\uA628\uA629",
["wara"] = "\U000118E0\U000118E1\U000118E2\U000118E3\U000118E4\U000118E5\U000118E6\U000118E7\U000118E8\U000118E9",
["wcho"] = "\U0001E2F0\U0001E2F1\U0001E2F2\U0001E2F3\U0001E2F4\U0001E2F5\U0001E2F6\U0001E2F7\U0001E2F8\U0001E2F9",
Expand Down
Loading