mirror of https://github.com/tc39/test262.git
1816 lines
47 KiB
JavaScript
1816 lines
47 KiB
JavaScript
// Copyright (C) 2011 2012 Norbert Lindenberg. All rights reserved.
|
||
// Copyright (C) 2012 2013 Mozilla Corporation. All rights reserved.
|
||
// This code is governed by the BSD license found in the LICENSE file.
|
||
/*---
|
||
description: |
|
||
This file contains shared functions for the tests in the conformance test
|
||
suite for the ECMAScript Internationalization API.
|
||
author: Norbert Lindenberg
|
||
---*/
|
||
|
||
/**
|
||
*/
|
||
|
||
|
||
/**
|
||
* @description Calls the provided function for every service constructor in
|
||
* the Intl object.
|
||
* @param {Function} f the function to call for each service constructor in
|
||
* the Intl object.
|
||
* @param {Function} Constructor the constructor object to test with.
|
||
*/
|
||
function testWithIntlConstructors(f) {
|
||
var constructors = ["Collator", "NumberFormat", "DateTimeFormat"];
|
||
|
||
// Optionally supported Intl constructors.
|
||
["PluralRules"].forEach(function(constructor) {
|
||
if (typeof Intl[constructor] === "function") {
|
||
constructors[constructors.length] = constructor;
|
||
}
|
||
});
|
||
|
||
constructors.forEach(function (constructor) {
|
||
var Constructor = Intl[constructor];
|
||
try {
|
||
f(Constructor);
|
||
} catch (e) {
|
||
e.message += " (Testing with " + constructor + ".)";
|
||
throw e;
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Taints a named data property of the given object by installing
|
||
* a setter that throws an exception.
|
||
* @param {object} obj the object whose data property to taint
|
||
* @param {string} property the property to taint
|
||
*/
|
||
function taintDataProperty(obj, property) {
|
||
Object.defineProperty(obj, property, {
|
||
set: function(value) {
|
||
$ERROR("Client code can adversely affect behavior: setter for " + property + ".");
|
||
},
|
||
enumerable: false,
|
||
configurable: true
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Taints a named method of the given object by replacing it with a function
|
||
* that throws an exception.
|
||
* @param {object} obj the object whose method to taint
|
||
* @param {string} property the name of the method to taint
|
||
*/
|
||
function taintMethod(obj, property) {
|
||
Object.defineProperty(obj, property, {
|
||
value: function() {
|
||
$ERROR("Client code can adversely affect behavior: method " + property + ".");
|
||
},
|
||
writable: true,
|
||
enumerable: false,
|
||
configurable: true
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Taints the given properties (and similarly named properties) by installing
|
||
* setters on Object.prototype that throw exceptions.
|
||
* @param {Array} properties an array of property names to taint
|
||
*/
|
||
function taintProperties(properties) {
|
||
properties.forEach(function (property) {
|
||
var adaptedProperties = [property, "__" + property, "_" + property, property + "_", property + "__"];
|
||
adaptedProperties.forEach(function (property) {
|
||
taintDataProperty(Object.prototype, property);
|
||
});
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Taints the Array object by creating a setter for the property "0" and
|
||
* replacing some key methods with functions that throw exceptions.
|
||
*/
|
||
function taintArray() {
|
||
taintDataProperty(Array.prototype, "0");
|
||
taintMethod(Array.prototype, "indexOf");
|
||
taintMethod(Array.prototype, "join");
|
||
taintMethod(Array.prototype, "push");
|
||
taintMethod(Array.prototype, "slice");
|
||
taintMethod(Array.prototype, "sort");
|
||
}
|
||
|
||
|
||
/**
|
||
* Gets locale support info for the given constructor object, which must be one
|
||
* of Intl constructors.
|
||
* @param {object} Constructor the constructor for which to get locale support info
|
||
* @return {object} locale support info with the following properties:
|
||
* supported: array of fully supported language tags
|
||
* byFallback: array of language tags that are supported through fallbacks
|
||
* unsupported: array of unsupported language tags
|
||
*/
|
||
function getLocaleSupportInfo(Constructor) {
|
||
var languages = ["zh", "es", "en", "hi", "ur", "ar", "ja", "pa"];
|
||
var scripts = ["Latn", "Hans", "Deva", "Arab", "Jpan", "Hant", "Guru"];
|
||
var countries = ["CN", "IN", "US", "PK", "JP", "TW", "HK", "SG", "419"];
|
||
|
||
var allTags = [];
|
||
var i, j, k;
|
||
var language, script, country;
|
||
for (i = 0; i < languages.length; i++) {
|
||
language = languages[i];
|
||
allTags.push(language);
|
||
for (j = 0; j < scripts.length; j++) {
|
||
script = scripts[j];
|
||
allTags.push(language + "-" + script);
|
||
for (k = 0; k < countries.length; k++) {
|
||
country = countries[k];
|
||
allTags.push(language + "-" + script + "-" + country);
|
||
}
|
||
}
|
||
for (k = 0; k < countries.length; k++) {
|
||
country = countries[k];
|
||
allTags.push(language + "-" + country);
|
||
}
|
||
}
|
||
|
||
var supported = [];
|
||
var byFallback = [];
|
||
var unsupported = [];
|
||
for (i = 0; i < allTags.length; i++) {
|
||
var request = allTags[i];
|
||
var result = new Constructor([request], {localeMatcher: "lookup"}).resolvedOptions().locale;
|
||
if (request === result) {
|
||
supported.push(request);
|
||
} else if (request.indexOf(result) === 0) {
|
||
byFallback.push(request);
|
||
} else {
|
||
unsupported.push(request);
|
||
}
|
||
}
|
||
|
||
return {
|
||
supported: supported,
|
||
byFallback: byFallback,
|
||
unsupported: unsupported
|
||
};
|
||
}
|
||
|
||
|
||
/**
|
||
* Returns an array of strings for which IsStructurallyValidLanguageTag() returns false
|
||
*/
|
||
function getInvalidLanguageTags() {
|
||
var invalidLanguageTags = [
|
||
"", // empty tag
|
||
"i", // singleton alone
|
||
"x", // private use without subtag
|
||
"u", // extension singleton in first place
|
||
"419", // region code in first place
|
||
"u-nu-latn-cu-bob", // extension sequence without language
|
||
"hans-cmn-cn", // "hans" could theoretically be a 4-letter language code,
|
||
// but those can't be followed by extlang codes.
|
||
"cmn-hans-cn-u-u", // duplicate singleton
|
||
"cmn-hans-cn-t-u-ca-u", // duplicate singleton
|
||
"de-gregory-gregory", // duplicate variant
|
||
"*", // language range
|
||
"de-*", // language range
|
||
"中文", // non-ASCII letters
|
||
"en-ß", // non-ASCII letters
|
||
"ıd", // non-ASCII letters
|
||
"es-Latn-latn", // two scripts
|
||
"pl-PL-pl", // two regions
|
||
"u-ca-gregory", // extension in first place
|
||
"de-1996-1996", // duplicate numeric variant
|
||
"pt-u-ca-gregory-u-nu-latn", // duplicate singleton subtag
|
||
|
||
// Invalid tags starting with: https://github.com/tc39/ecma402/pull/289
|
||
"no-nyn", // regular grandfathered in BCP47, but invalid in UTS35
|
||
"i-klingon", // irregular grandfathered in BCP47, but invalid in UTS35
|
||
"zh-hak-CN", // language with extlang in BCP47, but invalid in UTS35
|
||
"sgn-ils", // language with extlang in BCP47, but invalid in UTS35
|
||
"x-foo", // privateuse-only in BCP47, but invalid in UTS35
|
||
"x-en-US-12345", // more privateuse-only variants.
|
||
"x-12345-12345-en-US",
|
||
"x-en-US-12345-12345",
|
||
"x-en-u-foo",
|
||
"x-en-u-foo-u-bar",
|
||
"x-u-foo",
|
||
|
||
// underscores in different parts of the language tag
|
||
"de_DE",
|
||
"DE_de",
|
||
"cmn_Hans",
|
||
"cmn-hans_cn",
|
||
"es_419",
|
||
"es-419-u-nu-latn-cu_bob",
|
||
"i_klingon",
|
||
"cmn-hans-cn-t-ca-u-ca-x_t-u",
|
||
"enochian_enochian",
|
||
"de-gregory_u-ca-gregory",
|
||
|
||
"en\u0000", // null-terminator sequence
|
||
" en", // leading whitespace
|
||
"en ", // trailing whitespace
|
||
"it-IT-Latn", // country before script tag
|
||
"de-u", // incomplete Unicode extension sequences
|
||
"de-u-",
|
||
"de-u-ca-",
|
||
"de-u-ca-gregory-",
|
||
"si-x", // incomplete private-use tags
|
||
"x-",
|
||
"x-y-",
|
||
];
|
||
|
||
// make sure the data above is correct
|
||
for (var i = 0; i < invalidLanguageTags.length; ++i) {
|
||
var invalidTag = invalidLanguageTags[i];
|
||
assert(
|
||
!isCanonicalizedStructurallyValidLanguageTag(invalidTag),
|
||
"Test data \"" + invalidTag + "\" is a canonicalized and structurally valid language tag."
|
||
);
|
||
}
|
||
|
||
return invalidLanguageTags;
|
||
}
|
||
|
||
|
||
/**
|
||
* @description Tests whether locale is a String value representing a
|
||
* structurally valid and canonicalized BCP 47 language tag, as defined in
|
||
* sections 6.2.2 and 6.2.3 of the ECMAScript Internationalization API
|
||
* Specification.
|
||
* @param {String} locale the string to be tested.
|
||
* @result {Boolean} whether the test succeeded.
|
||
*/
|
||
function isCanonicalizedStructurallyValidLanguageTag(locale) {
|
||
|
||
/**
|
||
* Regular expression defining Unicode BCP 47 Locale Identifiers.
|
||
*
|
||
* Spec: https://unicode.org/reports/tr35/#Unicode_locale_identifier
|
||
*/
|
||
var alpha = "[a-z]",
|
||
digit = "[0-9]",
|
||
alphanum = "(" + alpha + "|" + digit + ")",
|
||
variant = "(" + alphanum + "{5,8}|(" + digit + alphanum + "{3}))",
|
||
region = "(" + alpha + "{2}|" + digit + "{3})",
|
||
script = "(" + alpha + "{4})",
|
||
language = "(" + alpha + "{2,3}|" + alpha + "{5,8})",
|
||
privateuse = "(x(-[a-z0-9]{1,8})+)",
|
||
singleton = "(" + digit + "|[a-wy-z])",
|
||
attribute= "(" + alphanum + "{3,8})",
|
||
keyword = "(" + alphanum + alpha + "(-" + alphanum + "{3,8})*)",
|
||
unicode_locale_extensions = "(u((-" + keyword + ")+|((-" + attribute + ")+(-" + keyword + ")*)))",
|
||
tlang = "(" + language + "(-" + script + ")?(-" + region + ")?(-" + variant + ")*)",
|
||
tfield = "(" + alpha + digit + "(-" + alphanum + "{3,8})+)",
|
||
transformed_extensions = "(t((-" + tlang + "(-" + tfield + ")*)|(-" + tfield + ")+))",
|
||
other_singleton = "(" + digit + "|[a-sv-wy-z])",
|
||
other_extensions = "(" + other_singleton + "(-" + alphanum + "{2,8})+)",
|
||
extension = "(" + unicode_locale_extensions + "|" + transformed_extensions + "|" + other_extensions + ")",
|
||
locale_id = language + "(-" + script + ")?(-" + region + ")?(-" + variant + ")*(-" + extension + ")*(-" + privateuse + ")?",
|
||
languageTag = "^(" + locale_id + ")$",
|
||
languageTagRE = new RegExp(languageTag, "i");
|
||
|
||
var duplicateSingleton = "-" + singleton + "-(.*-)?\\1(?!" + alphanum + ")",
|
||
duplicateSingletonRE = new RegExp(duplicateSingleton, "i"),
|
||
duplicateVariant = "(" + alphanum + "{2,8}-)+" + variant + "-(" + alphanum + "{2,8}-)*\\3(?!" + alphanum + ")",
|
||
duplicateVariantRE = new RegExp(duplicateVariant, "i");
|
||
|
||
|
||
/**
|
||
* Verifies that the given string is a well-formed Unicode BCP 47 Locale Identifier
|
||
* with no duplicate variant or singleton subtags.
|
||
*
|
||
* Spec: ECMAScript Internationalization API Specification, draft, 6.2.2.
|
||
*/
|
||
function isStructurallyValidLanguageTag(locale) {
|
||
if (!languageTagRE.test(locale)) {
|
||
return false;
|
||
}
|
||
locale = locale.split(/-x-/)[0];
|
||
return !duplicateSingletonRE.test(locale) && !duplicateVariantRE.test(locale);
|
||
}
|
||
|
||
|
||
/**
|
||
* Mappings from complete tags to preferred values.
|
||
*
|
||
* Spec: http://unicode.org/reports/tr35/#Identifiers
|
||
* Version: CLDR, version 35
|
||
*/
|
||
var __tagMappings = {
|
||
// property names must be in lower case; values in canonical form
|
||
|
||
"art-lojban": "jbo",
|
||
"cel-gaulish": "xtg-x-cel-gaulish",
|
||
"zh-guoyu": "zh",
|
||
"zh-hakka": "hak",
|
||
"zh-xiang": "hsn",
|
||
};
|
||
|
||
|
||
/**
|
||
* Mappings from language subtags to preferred values.
|
||
*
|
||
* Spec: http://unicode.org/reports/tr35/#Identifiers
|
||
* Version: CLDR, version 35
|
||
*/
|
||
var __languageMappings = {
|
||
// property names and values must be in canonical case
|
||
|
||
"aam": "aas",
|
||
"aar": "aa",
|
||
"abk": "ab",
|
||
"adp": "dz",
|
||
"afr": "af",
|
||
"aju": "jrb",
|
||
"aka": "ak",
|
||
"alb": "sq",
|
||
"als": "sq",
|
||
"amh": "am",
|
||
"ara": "ar",
|
||
"arb": "ar",
|
||
"arg": "an",
|
||
"arm": "hy",
|
||
"asm": "as",
|
||
"aue": "ktz",
|
||
"ava": "av",
|
||
"ave": "ae",
|
||
"aym": "ay",
|
||
"ayr": "ay",
|
||
"ayx": "nun",
|
||
"aze": "az",
|
||
"azj": "az",
|
||
"bak": "ba",
|
||
"bam": "bm",
|
||
"baq": "eu",
|
||
"bcc": "bal",
|
||
"bcl": "bik",
|
||
"bel": "be",
|
||
"ben": "bn",
|
||
"bgm": "bcg",
|
||
"bh": "bho",
|
||
"bih": "bho",
|
||
"bis": "bi",
|
||
"bjd": "drl",
|
||
"bod": "bo",
|
||
"bos": "bs",
|
||
"bre": "br",
|
||
"bul": "bg",
|
||
"bur": "my",
|
||
"bxk": "luy",
|
||
"bxr": "bua",
|
||
"cat": "ca",
|
||
"ccq": "rki",
|
||
"ces": "cs",
|
||
"cha": "ch",
|
||
"che": "ce",
|
||
"chi": "zh",
|
||
"chu": "cu",
|
||
"chv": "cv",
|
||
"cjr": "mom",
|
||
"cka": "cmr",
|
||
"cld": "syr",
|
||
"cmk": "xch",
|
||
"cmn": "zh",
|
||
"cor": "kw",
|
||
"cos": "co",
|
||
"coy": "pij",
|
||
"cqu": "quh",
|
||
"cre": "cr",
|
||
"cwd": "cr",
|
||
"cym": "cy",
|
||
"cze": "cs",
|
||
"dan": "da",
|
||
"deu": "de",
|
||
"dgo": "doi",
|
||
"dhd": "mwr",
|
||
"dik": "din",
|
||
"diq": "zza",
|
||
"div": "dv",
|
||
"drh": "mn",
|
||
"dut": "nl",
|
||
"dzo": "dz",
|
||
"ekk": "et",
|
||
"ell": "el",
|
||
"emk": "man",
|
||
"eng": "en",
|
||
"epo": "eo",
|
||
"esk": "ik",
|
||
"est": "et",
|
||
"eus": "eu",
|
||
"ewe": "ee",
|
||
"fao": "fo",
|
||
"fas": "fa",
|
||
"fat": "ak",
|
||
"fij": "fj",
|
||
"fin": "fi",
|
||
"fra": "fr",
|
||
"fre": "fr",
|
||
"fry": "fy",
|
||
"fuc": "ff",
|
||
"ful": "ff",
|
||
"gav": "dev",
|
||
"gaz": "om",
|
||
"gbo": "grb",
|
||
"geo": "ka",
|
||
"ger": "de",
|
||
"gfx": "vaj",
|
||
"ggn": "gvr",
|
||
"gla": "gd",
|
||
"gle": "ga",
|
||
"glg": "gl",
|
||
"glv": "gv",
|
||
"gno": "gon",
|
||
"gre": "el",
|
||
"grn": "gn",
|
||
"gti": "nyc",
|
||
"gug": "gn",
|
||
"guj": "gu",
|
||
"guv": "duz",
|
||
"gya": "gba",
|
||
"hat": "ht",
|
||
"hau": "ha",
|
||
"hdn": "hai",
|
||
"hea": "hmn",
|
||
"heb": "he",
|
||
"her": "hz",
|
||
"him": "srx",
|
||
"hin": "hi",
|
||
"hmo": "ho",
|
||
"hrr": "jal",
|
||
"hrv": "hr",
|
||
"hun": "hu",
|
||
"hye": "hy",
|
||
"ibi": "opa",
|
||
"ibo": "ig",
|
||
"ice": "is",
|
||
"ido": "io",
|
||
"iii": "ii",
|
||
"ike": "iu",
|
||
"iku": "iu",
|
||
"ile": "ie",
|
||
"ilw": "gal",
|
||
"in": "id",
|
||
"ina": "ia",
|
||
"ind": "id",
|
||
"ipk": "ik",
|
||
"isl": "is",
|
||
"ita": "it",
|
||
"iw": "he",
|
||
"jav": "jv",
|
||
"jeg": "oyb",
|
||
"ji": "yi",
|
||
"jpn": "ja",
|
||
"jw": "jv",
|
||
"kal": "kl",
|
||
"kan": "kn",
|
||
"kas": "ks",
|
||
"kat": "ka",
|
||
"kau": "kr",
|
||
"kaz": "kk",
|
||
"kgc": "tdf",
|
||
"kgh": "kml",
|
||
"khk": "mn",
|
||
"khm": "km",
|
||
"kik": "ki",
|
||
"kin": "rw",
|
||
"kir": "ky",
|
||
"kmr": "ku",
|
||
"knc": "kr",
|
||
"kng": "kg",
|
||
"knn": "kok",
|
||
"koj": "kwv",
|
||
"kom": "kv",
|
||
"kon": "kg",
|
||
"kor": "ko",
|
||
"kpv": "kv",
|
||
"krm": "bmf",
|
||
"ktr": "dtp",
|
||
"kua": "kj",
|
||
"kur": "ku",
|
||
"kvs": "gdj",
|
||
"kwq": "yam",
|
||
"kxe": "tvd",
|
||
"kzj": "dtp",
|
||
"kzt": "dtp",
|
||
"lao": "lo",
|
||
"lat": "la",
|
||
"lav": "lv",
|
||
"lbk": "bnc",
|
||
"lii": "raq",
|
||
"lim": "li",
|
||
"lin": "ln",
|
||
"lit": "lt",
|
||
"lmm": "rmx",
|
||
"ltz": "lb",
|
||
"lub": "lu",
|
||
"lug": "lg",
|
||
"lvs": "lv",
|
||
"mac": "mk",
|
||
"mah": "mh",
|
||
"mal": "ml",
|
||
"mao": "mi",
|
||
"mar": "mr",
|
||
"may": "ms",
|
||
"meg": "cir",
|
||
"mhr": "chm",
|
||
"mkd": "mk",
|
||
"mlg": "mg",
|
||
"mlt": "mt",
|
||
"mnk": "man",
|
||
"mo": "ro",
|
||
"mol": "ro",
|
||
"mon": "mn",
|
||
"mri": "mi",
|
||
"msa": "ms",
|
||
"mst": "mry",
|
||
"mup": "raj",
|
||
"mwj": "vaj",
|
||
"mya": "my",
|
||
"myt": "mry",
|
||
"nad": "xny",
|
||
"nau": "na",
|
||
"nav": "nv",
|
||
"nbl": "nr",
|
||
"ncp": "kdz",
|
||
"nde": "nd",
|
||
"ndo": "ng",
|
||
"nep": "ne",
|
||
"nld": "nl",
|
||
"nno": "nn",
|
||
"nnx": "ngv",
|
||
"no": "nb",
|
||
"nob": "nb",
|
||
"nor": "nb",
|
||
"npi": "ne",
|
||
"nts": "pij",
|
||
"nya": "ny",
|
||
"oci": "oc",
|
||
"ojg": "oj",
|
||
"oji": "oj",
|
||
"ori": "or",
|
||
"orm": "om",
|
||
"ory": "or",
|
||
"oss": "os",
|
||
"oun": "vaj",
|
||
"pan": "pa",
|
||
"pbu": "ps",
|
||
"pcr": "adx",
|
||
"per": "fa",
|
||
"pes": "fa",
|
||
"pli": "pi",
|
||
"plt": "mg",
|
||
"pmc": "huw",
|
||
"pmu": "phr",
|
||
"pnb": "lah",
|
||
"pol": "pl",
|
||
"por": "pt",
|
||
"ppa": "bfy",
|
||
"ppr": "lcq",
|
||
"pry": "prt",
|
||
"pus": "ps",
|
||
"puz": "pub",
|
||
"que": "qu",
|
||
"quz": "qu",
|
||
"rmy": "rom",
|
||
"roh": "rm",
|
||
"ron": "ro",
|
||
"rum": "ro",
|
||
"run": "rn",
|
||
"rus": "ru",
|
||
"sag": "sg",
|
||
"san": "sa",
|
||
"sca": "hle",
|
||
"scc": "sr",
|
||
"scr": "hr",
|
||
"sin": "si",
|
||
"skk": "oyb",
|
||
"slk": "sk",
|
||
"slo": "sk",
|
||
"slv": "sl",
|
||
"sme": "se",
|
||
"smo": "sm",
|
||
"sna": "sn",
|
||
"snd": "sd",
|
||
"som": "so",
|
||
"sot": "st",
|
||
"spa": "es",
|
||
"spy": "kln",
|
||
"sqi": "sq",
|
||
"src": "sc",
|
||
"srd": "sc",
|
||
"srp": "sr",
|
||
"ssw": "ss",
|
||
"sun": "su",
|
||
"swa": "sw",
|
||
"swe": "sv",
|
||
"swh": "sw",
|
||
"tah": "ty",
|
||
"tam": "ta",
|
||
"tat": "tt",
|
||
"tdu": "dtp",
|
||
"tel": "te",
|
||
"tgk": "tg",
|
||
"tgl": "fil",
|
||
"tha": "th",
|
||
"thc": "tpo",
|
||
"thx": "oyb",
|
||
"tib": "bo",
|
||
"tie": "ras",
|
||
"tir": "ti",
|
||
"tkk": "twm",
|
||
"tl": "fil",
|
||
"tlw": "weo",
|
||
"tmp": "tyj",
|
||
"tne": "kak",
|
||
"ton": "to",
|
||
"tsf": "taj",
|
||
"tsn": "tn",
|
||
"tso": "ts",
|
||
"ttq": "tmh",
|
||
"tuk": "tk",
|
||
"tur": "tr",
|
||
"tw": "ak",
|
||
"twi": "ak",
|
||
"uig": "ug",
|
||
"ukr": "uk",
|
||
"umu": "del",
|
||
"uok": "ema",
|
||
"urd": "ur",
|
||
"uzb": "uz",
|
||
"uzn": "uz",
|
||
"ven": "ve",
|
||
"vie": "vi",
|
||
"vol": "vo",
|
||
"wel": "cy",
|
||
"wln": "wa",
|
||
"wol": "wo",
|
||
"xba": "cax",
|
||
"xho": "xh",
|
||
"xia": "acn",
|
||
"xkh": "waw",
|
||
"xpe": "kpe",
|
||
"xsj": "suj",
|
||
"xsl": "den",
|
||
"ybd": "rki",
|
||
"ydd": "yi",
|
||
"yid": "yi",
|
||
"yma": "lrr",
|
||
"ymt": "mtm",
|
||
"yor": "yo",
|
||
"yos": "zom",
|
||
"yuu": "yug",
|
||
"zai": "zap",
|
||
"zha": "za",
|
||
"zho": "zh",
|
||
"zsm": "ms",
|
||
"zul": "zu",
|
||
"zyb": "za",
|
||
}
|
||
|
||
|
||
/**
|
||
* Mappings from region subtags to preferred values.
|
||
*
|
||
* Spec: http://unicode.org/reports/tr35/#Identifiers
|
||
* Version: CLDR, version 35
|
||
*/
|
||
var __regionMappings = {
|
||
// property names and values must be in canonical case
|
||
|
||
"004": "AF",
|
||
"008": "AL",
|
||
"010": "AQ",
|
||
"012": "DZ",
|
||
"016": "AS",
|
||
"020": "AD",
|
||
"024": "AO",
|
||
"028": "AG",
|
||
"031": "AZ",
|
||
"032": "AR",
|
||
"036": "AU",
|
||
"040": "AT",
|
||
"044": "BS",
|
||
"048": "BH",
|
||
"050": "BD",
|
||
"051": "AM",
|
||
"052": "BB",
|
||
"056": "BE",
|
||
"060": "BM",
|
||
"062": "034",
|
||
"064": "BT",
|
||
"068": "BO",
|
||
"070": "BA",
|
||
"072": "BW",
|
||
"074": "BV",
|
||
"076": "BR",
|
||
"084": "BZ",
|
||
"086": "IO",
|
||
"090": "SB",
|
||
"092": "VG",
|
||
"096": "BN",
|
||
"100": "BG",
|
||
"104": "MM",
|
||
"108": "BI",
|
||
"112": "BY",
|
||
"116": "KH",
|
||
"120": "CM",
|
||
"124": "CA",
|
||
"132": "CV",
|
||
"136": "KY",
|
||
"140": "CF",
|
||
"144": "LK",
|
||
"148": "TD",
|
||
"152": "CL",
|
||
"156": "CN",
|
||
"158": "TW",
|
||
"162": "CX",
|
||
"166": "CC",
|
||
"170": "CO",
|
||
"174": "KM",
|
||
"175": "YT",
|
||
"178": "CG",
|
||
"180": "CD",
|
||
"184": "CK",
|
||
"188": "CR",
|
||
"191": "HR",
|
||
"192": "CU",
|
||
"196": "CY",
|
||
"203": "CZ",
|
||
"204": "BJ",
|
||
"208": "DK",
|
||
"212": "DM",
|
||
"214": "DO",
|
||
"218": "EC",
|
||
"222": "SV",
|
||
"226": "GQ",
|
||
"230": "ET",
|
||
"231": "ET",
|
||
"232": "ER",
|
||
"233": "EE",
|
||
"234": "FO",
|
||
"238": "FK",
|
||
"239": "GS",
|
||
"242": "FJ",
|
||
"246": "FI",
|
||
"248": "AX",
|
||
"249": "FR",
|
||
"250": "FR",
|
||
"254": "GF",
|
||
"258": "PF",
|
||
"260": "TF",
|
||
"262": "DJ",
|
||
"266": "GA",
|
||
"268": "GE",
|
||
"270": "GM",
|
||
"275": "PS",
|
||
"276": "DE",
|
||
"278": "DE",
|
||
"280": "DE",
|
||
"288": "GH",
|
||
"292": "GI",
|
||
"296": "KI",
|
||
"300": "GR",
|
||
"304": "GL",
|
||
"308": "GD",
|
||
"312": "GP",
|
||
"316": "GU",
|
||
"320": "GT",
|
||
"324": "GN",
|
||
"328": "GY",
|
||
"332": "HT",
|
||
"334": "HM",
|
||
"336": "VA",
|
||
"340": "HN",
|
||
"344": "HK",
|
||
"348": "HU",
|
||
"352": "IS",
|
||
"356": "IN",
|
||
"360": "ID",
|
||
"364": "IR",
|
||
"368": "IQ",
|
||
"372": "IE",
|
||
"376": "IL",
|
||
"380": "IT",
|
||
"384": "CI",
|
||
"388": "JM",
|
||
"392": "JP",
|
||
"398": "KZ",
|
||
"400": "JO",
|
||
"404": "KE",
|
||
"408": "KP",
|
||
"410": "KR",
|
||
"414": "KW",
|
||
"417": "KG",
|
||
"418": "LA",
|
||
"422": "LB",
|
||
"426": "LS",
|
||
"428": "LV",
|
||
"430": "LR",
|
||
"434": "LY",
|
||
"438": "LI",
|
||
"440": "LT",
|
||
"442": "LU",
|
||
"446": "MO",
|
||
"450": "MG",
|
||
"454": "MW",
|
||
"458": "MY",
|
||
"462": "MV",
|
||
"466": "ML",
|
||
"470": "MT",
|
||
"474": "MQ",
|
||
"478": "MR",
|
||
"480": "MU",
|
||
"484": "MX",
|
||
"492": "MC",
|
||
"496": "MN",
|
||
"498": "MD",
|
||
"499": "ME",
|
||
"500": "MS",
|
||
"504": "MA",
|
||
"508": "MZ",
|
||
"512": "OM",
|
||
"516": "NA",
|
||
"520": "NR",
|
||
"524": "NP",
|
||
"528": "NL",
|
||
"531": "CW",
|
||
"533": "AW",
|
||
"534": "SX",
|
||
"535": "BQ",
|
||
"540": "NC",
|
||
"548": "VU",
|
||
"554": "NZ",
|
||
"558": "NI",
|
||
"562": "NE",
|
||
"566": "NG",
|
||
"570": "NU",
|
||
"574": "NF",
|
||
"578": "NO",
|
||
"580": "MP",
|
||
"581": "UM",
|
||
"583": "FM",
|
||
"584": "MH",
|
||
"585": "PW",
|
||
"586": "PK",
|
||
"591": "PA",
|
||
"598": "PG",
|
||
"600": "PY",
|
||
"604": "PE",
|
||
"608": "PH",
|
||
"612": "PN",
|
||
"616": "PL",
|
||
"620": "PT",
|
||
"624": "GW",
|
||
"626": "TL",
|
||
"630": "PR",
|
||
"634": "QA",
|
||
"638": "RE",
|
||
"642": "RO",
|
||
"643": "RU",
|
||
"646": "RW",
|
||
"652": "BL",
|
||
"654": "SH",
|
||
"659": "KN",
|
||
"660": "AI",
|
||
"662": "LC",
|
||
"663": "MF",
|
||
"666": "PM",
|
||
"670": "VC",
|
||
"674": "SM",
|
||
"678": "ST",
|
||
"682": "SA",
|
||
"686": "SN",
|
||
"688": "RS",
|
||
"690": "SC",
|
||
"694": "SL",
|
||
"702": "SG",
|
||
"703": "SK",
|
||
"704": "VN",
|
||
"705": "SI",
|
||
"706": "SO",
|
||
"710": "ZA",
|
||
"716": "ZW",
|
||
"720": "YE",
|
||
"724": "ES",
|
||
"728": "SS",
|
||
"729": "SD",
|
||
"732": "EH",
|
||
"736": "SD",
|
||
"740": "SR",
|
||
"744": "SJ",
|
||
"748": "SZ",
|
||
"752": "SE",
|
||
"756": "CH",
|
||
"760": "SY",
|
||
"762": "TJ",
|
||
"764": "TH",
|
||
"768": "TG",
|
||
"772": "TK",
|
||
"776": "TO",
|
||
"780": "TT",
|
||
"784": "AE",
|
||
"788": "TN",
|
||
"792": "TR",
|
||
"795": "TM",
|
||
"796": "TC",
|
||
"798": "TV",
|
||
"800": "UG",
|
||
"804": "UA",
|
||
"807": "MK",
|
||
"818": "EG",
|
||
"826": "GB",
|
||
"830": "JE",
|
||
"831": "GG",
|
||
"832": "JE",
|
||
"833": "IM",
|
||
"834": "TZ",
|
||
"840": "US",
|
||
"850": "VI",
|
||
"854": "BF",
|
||
"858": "UY",
|
||
"860": "UZ",
|
||
"862": "VE",
|
||
"876": "WF",
|
||
"882": "WS",
|
||
"886": "YE",
|
||
"887": "YE",
|
||
"891": "RS",
|
||
"894": "ZM",
|
||
"958": "AA",
|
||
"959": "QM",
|
||
"960": "QN",
|
||
"962": "QP",
|
||
"963": "QQ",
|
||
"964": "QR",
|
||
"965": "QS",
|
||
"966": "QT",
|
||
"967": "EU",
|
||
"968": "QV",
|
||
"969": "QW",
|
||
"970": "QX",
|
||
"971": "QY",
|
||
"972": "QZ",
|
||
"973": "XA",
|
||
"974": "XB",
|
||
"975": "XC",
|
||
"976": "XD",
|
||
"977": "XE",
|
||
"978": "XF",
|
||
"979": "XG",
|
||
"980": "XH",
|
||
"981": "XI",
|
||
"982": "XJ",
|
||
"983": "XK",
|
||
"984": "XL",
|
||
"985": "XM",
|
||
"986": "XN",
|
||
"987": "XO",
|
||
"988": "XP",
|
||
"989": "XQ",
|
||
"990": "XR",
|
||
"991": "XS",
|
||
"992": "XT",
|
||
"993": "XU",
|
||
"994": "XV",
|
||
"995": "XW",
|
||
"996": "XX",
|
||
"997": "XY",
|
||
"998": "XZ",
|
||
"999": "ZZ",
|
||
"BU": "MM",
|
||
"CS": "RS",
|
||
"CT": "KI",
|
||
"DD": "DE",
|
||
"DY": "BJ",
|
||
"FQ": "AQ",
|
||
"FX": "FR",
|
||
"HV": "BF",
|
||
"JT": "UM",
|
||
"MI": "UM",
|
||
"NH": "VU",
|
||
"NQ": "AQ",
|
||
"PU": "UM",
|
||
"PZ": "PA",
|
||
"QU": "EU",
|
||
"RH": "ZW",
|
||
"TP": "TL",
|
||
"UK": "GB",
|
||
"VD": "VN",
|
||
"WK": "UM",
|
||
"YD": "YE",
|
||
"YU": "RS",
|
||
"ZR": "CD",
|
||
};
|
||
|
||
|
||
/**
|
||
* Canonicalizes the given well-formed BCP 47 language tag, including regularized case of subtags.
|
||
*
|
||
* Spec: ECMAScript Internationalization API Specification, draft, 6.2.3.
|
||
* Spec: RFC 5646, section 4.5.
|
||
*/
|
||
function canonicalizeLanguageTag(locale) {
|
||
|
||
// start with lower case for easier processing, and because most subtags will need to be lower case anyway
|
||
locale = locale.toLowerCase();
|
||
|
||
// handle mappings for complete tags
|
||
if (__tagMappings.hasOwnProperty(locale)) {
|
||
return __tagMappings[locale];
|
||
}
|
||
|
||
var subtags = locale.split("-");
|
||
var i = 0;
|
||
|
||
// handle standard part: all subtags before first variant or singleton subtag
|
||
var language;
|
||
var script;
|
||
var region;
|
||
while (i < subtags.length) {
|
||
var subtag = subtags[i];
|
||
if (i === 0) {
|
||
language = subtag;
|
||
} else if (subtag.length === 2 || subtag.length === 3) {
|
||
region = subtag.toUpperCase();
|
||
} else if (subtag.length === 4 && !("0" <= subtag[0] && subtag[0] <= "9")) {
|
||
script = subtag[0].toUpperCase() + subtag.substring(1).toLowerCase();
|
||
} else {
|
||
break;
|
||
}
|
||
i++;
|
||
}
|
||
|
||
if (__languageMappings.hasOwnProperty(language)) {
|
||
language = __languageMappings[language];
|
||
} else {
|
||
// Language subtags with complex mappings, CLDR 35.
|
||
switch (language) {
|
||
case "cnr":
|
||
language = "sr";
|
||
if (region === undefined) {
|
||
region = "ME";
|
||
}
|
||
break;
|
||
case "drw":
|
||
case "prs":
|
||
case "tnf":
|
||
language = "fa";
|
||
if (region === undefined) {
|
||
region = "AF";
|
||
}
|
||
break;
|
||
case "hbs":
|
||
case "sh":
|
||
language = "sr";
|
||
if (script === undefined) {
|
||
script = "Latn";
|
||
}
|
||
break;
|
||
case "swc":
|
||
language = "sw";
|
||
if (region === undefined) {
|
||
region = "CD";
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (region !== undefined) {
|
||
if (__regionMappings.hasOwnProperty(region)) {
|
||
region = __regionMappings[region];
|
||
} else {
|
||
// Region subtags with complex mappings, CLDR 35.
|
||
switch (region) {
|
||
case "172":
|
||
if (language === "ab" || language === "ka" || language === "os" ||
|
||
(language === "und" && script === "Geor") || language === "xmf") {
|
||
region = "GE";
|
||
}
|
||
else if (language === "az" || language === "tkr" || language === "tly" || language === "ttt") {
|
||
region = "AZ";
|
||
}
|
||
else if (language === "be") {
|
||
region = "BY";
|
||
}
|
||
else if (language === "crh" || language === "got" || language === "ji" || language === "rue" ||
|
||
language === "uk" || (language === "und" && script === "Goth")) {
|
||
region = "UA";
|
||
}
|
||
else if (language === "gag") {
|
||
region = "MD";
|
||
}
|
||
else if (language === "hy" || (language === "und" && script === "Armn")) {
|
||
region = "AM";
|
||
}
|
||
else if (language === "kaa" || language === "sog" || (language === "und" && script === "Sogd") ||
|
||
(language === "und" && script === "Sogo") || language === "uz") {
|
||
region = "UZ";
|
||
}
|
||
else if (language === "kk" || (language === "ug" && script === "Cyrl")) {
|
||
region = "KZ";
|
||
}
|
||
else if (language === "ky") {
|
||
region = "KG";
|
||
}
|
||
else if (language === "tg") {
|
||
region = "TJ";
|
||
}
|
||
else if (language === "tk") {
|
||
region = "TM";
|
||
}
|
||
else {
|
||
region = "RU";
|
||
}
|
||
break;
|
||
case "200":
|
||
if (language === "sk") {
|
||
region = "SK";
|
||
}
|
||
else {
|
||
region = "CZ";
|
||
}
|
||
break;
|
||
case "530":
|
||
case "532":
|
||
case "AN":
|
||
if (language === "vic") {
|
||
region = "SX";
|
||
}
|
||
else {
|
||
region = "CW";
|
||
}
|
||
break;
|
||
case "536":
|
||
case "NT":
|
||
if (language === "akk" || language === "ckb" || (language === "ku" && script === "Arab") ||
|
||
language === "mis" || language === "syr" || (language === "und" && script === "Xsux") ||
|
||
(language === "und" && script === "Hatr") || (language === "und" && script === "Syrc")) {
|
||
region = "IQ";
|
||
}
|
||
else {
|
||
region = "SA";
|
||
}
|
||
break;
|
||
case "582":
|
||
case "PC":
|
||
if (language === "mh") {
|
||
region = "MH";
|
||
}
|
||
else if (language === "pau") {
|
||
region = "PW";
|
||
}
|
||
else {
|
||
region = "FM";
|
||
}
|
||
break;
|
||
case "810":
|
||
case "SU":
|
||
if (language === "ab" || language === "ka" || language === "os" || language === "xmf" ||
|
||
(language === "und" && script === "Geor")) {
|
||
region = "GE";
|
||
}
|
||
else if (language === "az" || language === "tkr" || language === "tly" || language === "ttt") {
|
||
region = "AZ";
|
||
}
|
||
else if (language === "be") {
|
||
region = "BY";
|
||
}
|
||
else if (language === "crh" || language === "got" || language === "ji" || language === "rue" ||
|
||
language === "uk" || (language === "und" && script === "Goth")) {
|
||
region = "UA";
|
||
}
|
||
else if (language === "et" || language === "vro") {
|
||
region = "EE";
|
||
}
|
||
else if (language === "gag") {
|
||
region = "MD";
|
||
}
|
||
else if (language === "hy" || (language === "und" && script === "Armn")) {
|
||
region = "AM";
|
||
}
|
||
else if (language === "kaa" || language === "sog" || (language === "und" && script === "Sogd") ||
|
||
(language === "und" && script === "Sogo") || language === "uz") {
|
||
region = "UZ";
|
||
}
|
||
else if (language === "kk" || (language === "ug" && script === "Cyrl")) {
|
||
region = "KZ";
|
||
}
|
||
else if (language === "ky") {
|
||
region = "KG";
|
||
}
|
||
else if (language === "lt" || language === "sgs") {
|
||
region = "LT";
|
||
}
|
||
else if (language === "ltg" || language === "lv") {
|
||
region = "LV";
|
||
}
|
||
else if (language === "tg") {
|
||
region = "TJ";
|
||
}
|
||
else if (language === "tk") {
|
||
region = "TM";
|
||
}
|
||
else {
|
||
region = "RU";
|
||
}
|
||
break;
|
||
case "890":
|
||
if (language === "bs") {
|
||
region = "BA";
|
||
}
|
||
else if (language === "hr") {
|
||
region = "HR";
|
||
}
|
||
else if (language === "mk") {
|
||
region = "MK";
|
||
}
|
||
else if (language === "sl") {
|
||
region = "SI";
|
||
}
|
||
else {
|
||
region = "RS";
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// handle variants
|
||
var variants = [];
|
||
while (i < subtags.length && subtags[i].length > 1) {
|
||
variants.push(subtags[i]);
|
||
i += 1;
|
||
}
|
||
variants.sort();
|
||
|
||
// handle extensions
|
||
var extensions = [];
|
||
while (i < subtags.length && subtags[i] !== "x") {
|
||
var extensionStart = i;
|
||
i++;
|
||
while (i < subtags.length && subtags[i].length > 1) {
|
||
i++;
|
||
}
|
||
var extension = subtags.slice(extensionStart, i).join("-");
|
||
extensions.push(extension);
|
||
}
|
||
extensions.sort();
|
||
|
||
// handle private use
|
||
var privateUse;
|
||
if (i < subtags.length) {
|
||
privateUse = subtags.slice(i).join("-");
|
||
}
|
||
|
||
// put everything back together
|
||
var canonical = language;
|
||
if (script !== undefined) {
|
||
canonical += "-" + script;
|
||
}
|
||
if (region !== undefined) {
|
||
canonical += "-" + region;
|
||
}
|
||
if (variants.length > 0) {
|
||
canonical += "-" + variants.join("-");
|
||
}
|
||
if (extensions.length > 0) {
|
||
canonical += "-" + extensions.join("-");
|
||
}
|
||
if (privateUse !== undefined) {
|
||
if (canonical.length > 0) {
|
||
canonical += "-" + privateUse;
|
||
} else {
|
||
canonical = privateUse;
|
||
}
|
||
}
|
||
|
||
return canonical;
|
||
}
|
||
|
||
return typeof locale === "string" && isStructurallyValidLanguageTag(locale) &&
|
||
canonicalizeLanguageTag(locale) === locale;
|
||
}
|
||
|
||
|
||
/**
|
||
* Returns an array of error cases handled by CanonicalizeLocaleList().
|
||
*/
|
||
function getInvalidLocaleArguments() {
|
||
function CustomError() {}
|
||
|
||
var topLevelErrors = [
|
||
// fails ToObject
|
||
[null, TypeError],
|
||
|
||
// fails Get
|
||
[{ get length() { throw new CustomError(); } }, CustomError],
|
||
|
||
// fail ToLength
|
||
[{ length: Symbol.toPrimitive }, TypeError],
|
||
[{ length: { get [Symbol.toPrimitive]() { throw new CustomError(); } } }, CustomError],
|
||
[{ length: { [Symbol.toPrimitive]() { throw new CustomError(); } } }, CustomError],
|
||
[{ length: { get valueOf() { throw new CustomError(); } } }, CustomError],
|
||
[{ length: { valueOf() { throw new CustomError(); } } }, CustomError],
|
||
[{ length: { get toString() { throw new CustomError(); } } }, CustomError],
|
||
[{ length: { toString() { throw new CustomError(); } } }, CustomError],
|
||
|
||
// fail type check
|
||
[[undefined], TypeError],
|
||
[[null], TypeError],
|
||
[[true], TypeError],
|
||
[[Symbol.toPrimitive], TypeError],
|
||
[[1], TypeError],
|
||
[[0.1], TypeError],
|
||
[[NaN], TypeError],
|
||
];
|
||
|
||
var invalidLanguageTags = [
|
||
"", // empty tag
|
||
"i", // singleton alone
|
||
"x", // private use without subtag
|
||
"u", // extension singleton in first place
|
||
"419", // region code in first place
|
||
"u-nu-latn-cu-bob", // extension sequence without language
|
||
"hans-cmn-cn", // "hans" could theoretically be a 4-letter language code,
|
||
// but those can't be followed by extlang codes.
|
||
"abcdefghi", // overlong language
|
||
"cmn-hans-cn-u-u", // duplicate singleton
|
||
"cmn-hans-cn-t-u-ca-u", // duplicate singleton
|
||
"de-gregory-gregory", // duplicate variant
|
||
"*", // language range
|
||
"de-*", // language range
|
||
"中文", // non-ASCII letters
|
||
"en-ß", // non-ASCII letters
|
||
"ıd" // non-ASCII letters
|
||
];
|
||
|
||
return topLevelErrors.concat(
|
||
invalidLanguageTags.map(tag => [tag, RangeError]),
|
||
invalidLanguageTags.map(tag => [[tag], RangeError]),
|
||
invalidLanguageTags.map(tag => [["en", tag], RangeError]),
|
||
)
|
||
}
|
||
|
||
/**
|
||
* Tests whether the named options property is correctly handled by the given constructor.
|
||
* @param {object} Constructor the constructor to test.
|
||
* @param {string} property the name of the options property to test.
|
||
* @param {string} type the type that values of the property are expected to have
|
||
* @param {Array} [values] an array of allowed values for the property. Not needed for boolean.
|
||
* @param {any} fallback the fallback value that the property assumes if not provided.
|
||
* @param {object} testOptions additional options:
|
||
* @param {boolean} isOptional whether support for this property is optional for implementations.
|
||
* @param {boolean} noReturn whether the resulting value of the property is not returned.
|
||
* @param {boolean} isILD whether the resulting value of the property is implementation and locale dependent.
|
||
* @param {object} extra additional option to pass along, properties are value -> {option: value}.
|
||
*/
|
||
function testOption(Constructor, property, type, values, fallback, testOptions) {
|
||
var isOptional = testOptions !== undefined && testOptions.isOptional === true;
|
||
var noReturn = testOptions !== undefined && testOptions.noReturn === true;
|
||
var isILD = testOptions !== undefined && testOptions.isILD === true;
|
||
|
||
function addExtraOptions(options, value, testOptions) {
|
||
if (testOptions !== undefined && testOptions.extra !== undefined) {
|
||
var extra;
|
||
if (value !== undefined && testOptions.extra[value] !== undefined) {
|
||
extra = testOptions.extra[value];
|
||
} else if (testOptions.extra.any !== undefined) {
|
||
extra = testOptions.extra.any;
|
||
}
|
||
if (extra !== undefined) {
|
||
Object.getOwnPropertyNames(extra).forEach(function (prop) {
|
||
options[prop] = extra[prop];
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
var testValues, options, obj, expected, actual, error;
|
||
|
||
// test that the specified values are accepted. Also add values that convert to specified values.
|
||
if (type === "boolean") {
|
||
if (values === undefined) {
|
||
values = [true, false];
|
||
}
|
||
testValues = values.slice(0);
|
||
testValues.push(888);
|
||
testValues.push(0);
|
||
} else if (type === "string") {
|
||
testValues = values.slice(0);
|
||
testValues.push({toString: function () { return values[0]; }});
|
||
}
|
||
testValues.forEach(function (value) {
|
||
options = {};
|
||
options[property] = value;
|
||
addExtraOptions(options, value, testOptions);
|
||
obj = new Constructor(undefined, options);
|
||
if (noReturn) {
|
||
if (obj.resolvedOptions().hasOwnProperty(property)) {
|
||
$ERROR("Option property " + property + " is returned, but shouldn't be.");
|
||
}
|
||
} else {
|
||
actual = obj.resolvedOptions()[property];
|
||
if (isILD) {
|
||
if (actual !== undefined && values.indexOf(actual) === -1) {
|
||
$ERROR("Invalid value " + actual + " returned for property " + property + ".");
|
||
}
|
||
} else {
|
||
if (type === "boolean") {
|
||
expected = Boolean(value);
|
||
} else if (type === "string") {
|
||
expected = String(value);
|
||
}
|
||
if (actual !== expected && !(isOptional && actual === undefined)) {
|
||
$ERROR("Option value " + value + " for property " + property +
|
||
" was not accepted; got " + actual + " instead.");
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// test that invalid values are rejected
|
||
if (type === "string") {
|
||
var invalidValues = ["invalidValue", -1, null];
|
||
// assume that we won't have values in caseless scripts
|
||
if (values[0].toUpperCase() !== values[0]) {
|
||
invalidValues.push(values[0].toUpperCase());
|
||
} else {
|
||
invalidValues.push(values[0].toLowerCase());
|
||
}
|
||
invalidValues.forEach(function (value) {
|
||
options = {};
|
||
options[property] = value;
|
||
addExtraOptions(options, value, testOptions);
|
||
error = undefined;
|
||
try {
|
||
obj = new Constructor(undefined, options);
|
||
} catch (e) {
|
||
error = e;
|
||
}
|
||
if (error === undefined) {
|
||
$ERROR("Invalid option value " + value + " for property " + property + " was not rejected.");
|
||
} else if (error.name !== "RangeError") {
|
||
$ERROR("Invalid option value " + value + " for property " + property + " was rejected with wrong error " + error.name + ".");
|
||
}
|
||
});
|
||
}
|
||
|
||
// test that fallback value or another valid value is used if no options value is provided
|
||
if (!noReturn) {
|
||
options = {};
|
||
addExtraOptions(options, undefined, testOptions);
|
||
obj = new Constructor(undefined, options);
|
||
actual = obj.resolvedOptions()[property];
|
||
if (!(isOptional && actual === undefined)) {
|
||
if (fallback !== undefined) {
|
||
if (actual !== fallback) {
|
||
$ERROR("Option fallback value " + fallback + " for property " + property +
|
||
" was not used; got " + actual + " instead.");
|
||
}
|
||
} else {
|
||
if (values.indexOf(actual) === -1 && !(isILD && actual === undefined)) {
|
||
$ERROR("Invalid value " + actual + " returned for property " + property + ".");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Properties of the RegExp constructor that may be affected by use of regular
|
||
* expressions, and the default values of these properties. Properties are from
|
||
* https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Deprecated_and_obsolete_features#RegExp_Properties
|
||
*/
|
||
var regExpProperties = ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9",
|
||
"$_", "$*", "$&", "$+", "$`", "$'",
|
||
"input", "lastMatch", "lastParen", "leftContext", "rightContext"
|
||
];
|
||
|
||
var regExpPropertiesDefaultValues = (function () {
|
||
var values = Object.create(null);
|
||
regExpProperties.forEach(function (property) {
|
||
values[property] = RegExp[property];
|
||
});
|
||
return values;
|
||
}());
|
||
|
||
|
||
/**
|
||
* Tests that executing the provided function (which may use regular expressions
|
||
* in its implementation) does not create or modify unwanted properties on the
|
||
* RegExp constructor.
|
||
*/
|
||
function testForUnwantedRegExpChanges(testFunc) {
|
||
regExpProperties.forEach(function (property) {
|
||
RegExp[property] = regExpPropertiesDefaultValues[property];
|
||
});
|
||
testFunc();
|
||
regExpProperties.forEach(function (property) {
|
||
if (RegExp[property] !== regExpPropertiesDefaultValues[property]) {
|
||
$ERROR("RegExp has unexpected property " + property + " with value " +
|
||
RegExp[property] + ".");
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Tests whether name is a valid BCP 47 numbering system name
|
||
* and not excluded from use in the ECMAScript Internationalization API.
|
||
* @param {string} name the name to be tested.
|
||
* @return {boolean} whether name is a valid BCP 47 numbering system name and
|
||
* allowed for use in the ECMAScript Internationalization API.
|
||
*/
|
||
|
||
function isValidNumberingSystem(name) {
|
||
|
||
// source: CLDR file common/bcp47/number.xml; version CLDR 32.
|
||
var numberingSystems = [
|
||
"adlm",
|
||
"ahom",
|
||
"arab",
|
||
"arabext",
|
||
"armn",
|
||
"armnlow",
|
||
"bali",
|
||
"beng",
|
||
"bhks",
|
||
"brah",
|
||
"cakm",
|
||
"cham",
|
||
"cyrl",
|
||
"deva",
|
||
"ethi",
|
||
"finance",
|
||
"fullwide",
|
||
"geor",
|
||
"gonm",
|
||
"grek",
|
||
"greklow",
|
||
"gujr",
|
||
"guru",
|
||
"hanidays",
|
||
"hanidec",
|
||
"hans",
|
||
"hansfin",
|
||
"hant",
|
||
"hantfin",
|
||
"hebr",
|
||
"hmng",
|
||
"java",
|
||
"jpan",
|
||
"jpanfin",
|
||
"kali",
|
||
"khmr",
|
||
"knda",
|
||
"lana",
|
||
"lanatham",
|
||
"laoo",
|
||
"latn",
|
||
"lepc",
|
||
"limb",
|
||
"mathbold",
|
||
"mathdbl",
|
||
"mathmono",
|
||
"mathsanb",
|
||
"mathsans",
|
||
"mlym",
|
||
"modi",
|
||
"mong",
|
||
"mroo",
|
||
"mtei",
|
||
"mymr",
|
||
"mymrshan",
|
||
"mymrtlng",
|
||
"native",
|
||
"newa",
|
||
"nkoo",
|
||
"olck",
|
||
"orya",
|
||
"osma",
|
||
"roman",
|
||
"romanlow",
|
||
"saur",
|
||
"shrd",
|
||
"sind",
|
||
"sinh",
|
||
"sora",
|
||
"sund",
|
||
"takr",
|
||
"talu",
|
||
"taml",
|
||
"tamldec",
|
||
"telu",
|
||
"thai",
|
||
"tirh",
|
||
"tibt",
|
||
"traditio",
|
||
"vaii",
|
||
"wara",
|
||
];
|
||
|
||
var excluded = [
|
||
"finance",
|
||
"native",
|
||
"traditio"
|
||
];
|
||
|
||
|
||
return numberingSystems.indexOf(name) !== -1 && excluded.indexOf(name) === -1;
|
||
}
|
||
|
||
|
||
/**
|
||
* Provides the digits of numbering systems with simple digit mappings,
|
||
* as specified in 11.3.2.
|
||
*/
|
||
|
||
var numberingSystemDigits = {
|
||
arab: "٠١٢٣٤٥٦٧٨٩",
|
||
arabext: "۰۱۲۳۴۵۶۷۸۹",
|
||
bali: "\u1B50\u1B51\u1B52\u1B53\u1B54\u1B55\u1B56\u1B57\u1B58\u1B59",
|
||
beng: "০১২৩৪৫৬৭৮৯",
|
||
deva: "०१२३४५६७८९",
|
||
fullwide: "0123456789",
|
||
gujr: "૦૧૨૩૪૫૬૭૮૯",
|
||
guru: "੦੧੨੩੪੫੬੭੮੯",
|
||
hanidec: "〇一二三四五六七八九",
|
||
khmr: "០១២៣៤៥៦៧៨៩",
|
||
knda: "೦೧೨೩೪೫೬೭೮೯",
|
||
laoo: "໐໑໒໓໔໕໖໗໘໙",
|
||
latn: "0123456789",
|
||
limb: "\u1946\u1947\u1948\u1949\u194A\u194B\u194C\u194D\u194E\u194F",
|
||
mlym: "൦൧൨൩൪൫൬൭൮൯",
|
||
mong: "᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙",
|
||
mymr: "၀၁၂၃၄၅၆၇၈၉",
|
||
orya: "୦୧୨୩୪୫୬୭୮୯",
|
||
tamldec: "௦௧௨௩௪௫௬௭௮௯",
|
||
telu: "౦౧౨౩౪౫౬౭౮౯",
|
||
thai: "๐๑๒๓๔๕๖๗๘๙",
|
||
tibt: "༠༡༢༣༤༥༦༧༨༩"
|
||
};
|
||
|
||
|
||
/**
|
||
* Tests that number formatting is handled correctly. The function checks that the
|
||
* digit sequences in formatted output are as specified, converted to the
|
||
* selected numbering system, and embedded in consistent localized patterns.
|
||
* @param {Array} locales the locales to be tested.
|
||
* @param {Array} numberingSystems the numbering systems to be tested.
|
||
* @param {Object} options the options to pass to Intl.NumberFormat. Options
|
||
* must include {useGrouping: false}, and must cause 1.1 to be formatted
|
||
* pre- and post-decimal digits.
|
||
* @param {Object} testData maps input data (in ES5 9.3.1 format) to expected output strings
|
||
* in unlocalized format with Western digits.
|
||
*/
|
||
|
||
function testNumberFormat(locales, numberingSystems, options, testData) {
|
||
locales.forEach(function (locale) {
|
||
numberingSystems.forEach(function (numbering) {
|
||
var digits = numberingSystemDigits[numbering];
|
||
var format = new Intl.NumberFormat([locale + "-u-nu-" + numbering], options);
|
||
|
||
function getPatternParts(positive) {
|
||
var n = positive ? 1.1 : -1.1;
|
||
var formatted = format.format(n);
|
||
var oneoneRE = "([^" + digits + "]*)[" + digits + "]+([^" + digits + "]+)[" + digits + "]+([^" + digits + "]*)";
|
||
var match = formatted.match(new RegExp(oneoneRE));
|
||
if (match === null) {
|
||
$ERROR("Unexpected formatted " + n + " for " +
|
||
format.resolvedOptions().locale + " and options " +
|
||
JSON.stringify(options) + ": " + formatted);
|
||
}
|
||
return match;
|
||
}
|
||
|
||
function toNumbering(raw) {
|
||
return raw.replace(/[0-9]/g, function (digit) {
|
||
return digits[digit.charCodeAt(0) - "0".charCodeAt(0)];
|
||
});
|
||
}
|
||
|
||
function buildExpected(raw, patternParts) {
|
||
var period = raw.indexOf(".");
|
||
if (period === -1) {
|
||
return patternParts[1] + toNumbering(raw) + patternParts[3];
|
||
} else {
|
||
return patternParts[1] +
|
||
toNumbering(raw.substring(0, period)) +
|
||
patternParts[2] +
|
||
toNumbering(raw.substring(period + 1)) +
|
||
patternParts[3];
|
||
}
|
||
}
|
||
|
||
if (format.resolvedOptions().numberingSystem === numbering) {
|
||
// figure out prefixes, infixes, suffixes for positive and negative values
|
||
var posPatternParts = getPatternParts(true);
|
||
var negPatternParts = getPatternParts(false);
|
||
|
||
Object.getOwnPropertyNames(testData).forEach(function (input) {
|
||
var rawExpected = testData[input];
|
||
var patternParts;
|
||
if (rawExpected[0] === "-") {
|
||
patternParts = negPatternParts;
|
||
rawExpected = rawExpected.substring(1);
|
||
} else {
|
||
patternParts = posPatternParts;
|
||
}
|
||
var expected = buildExpected(rawExpected, patternParts);
|
||
var actual = format.format(input);
|
||
if (actual !== expected) {
|
||
$ERROR("Formatted value for " + input + ", " +
|
||
format.resolvedOptions().locale + " and options " +
|
||
JSON.stringify(options) + " is " + actual + "; expected " + expected + ".");
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Return the components of date-time formats.
|
||
* @return {Array} an array with all date-time components.
|
||
*/
|
||
|
||
function getDateTimeComponents() {
|
||
return ["weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName"];
|
||
}
|
||
|
||
|
||
/**
|
||
* Return the valid values for the given date-time component, as specified
|
||
* by the table in section 12.1.1.
|
||
* @param {string} component a date-time component.
|
||
* @return {Array} an array with the valid values for the component.
|
||
*/
|
||
|
||
function getDateTimeComponentValues(component) {
|
||
|
||
var components = {
|
||
weekday: ["narrow", "short", "long"],
|
||
era: ["narrow", "short", "long"],
|
||
year: ["2-digit", "numeric"],
|
||
month: ["2-digit", "numeric", "narrow", "short", "long"],
|
||
day: ["2-digit", "numeric"],
|
||
hour: ["2-digit", "numeric"],
|
||
minute: ["2-digit", "numeric"],
|
||
second: ["2-digit", "numeric"],
|
||
timeZoneName: ["short", "long"]
|
||
};
|
||
|
||
var result = components[component];
|
||
if (result === undefined) {
|
||
$ERROR("Internal error: No values defined for date-time component " + component + ".");
|
||
}
|
||
return result;
|
||
}
|
||
|
||
|
||
/**
|
||
* @description Tests whether timeZone is a String value representing a
|
||
* structurally valid and canonicalized time zone name, as defined in
|
||
* sections 6.4.1 and 6.4.2 of the ECMAScript Internationalization API
|
||
* Specification.
|
||
* @param {String} timeZone the string to be tested.
|
||
* @result {Boolean} whether the test succeeded.
|
||
*/
|
||
|
||
function isCanonicalizedStructurallyValidTimeZoneName(timeZone) {
|
||
/**
|
||
* Regular expression defining IANA Time Zone names.
|
||
*
|
||
* Spec: IANA Time Zone Database, Theory file
|
||
*/
|
||
var fileNameComponent = "(?:[A-Za-z_]|\\.(?!\\.?(?:/|$)))[A-Za-z.\\-_]{0,13}";
|
||
var fileName = fileNameComponent + "(?:/" + fileNameComponent + ")*";
|
||
var etcName = "(?:Etc/)?GMT[+-]\\d{1,2}";
|
||
var systemVName = "SystemV/[A-Z]{3}\\d{1,2}(?:[A-Z]{3})?";
|
||
var legacyName = etcName + "|" + systemVName + "|CST6CDT|EST5EDT|MST7MDT|PST8PDT|NZ";
|
||
var zoneNamePattern = new RegExp("^(?:" + fileName + "|" + legacyName + ")$");
|
||
|
||
if (typeof timeZone !== "string") {
|
||
return false;
|
||
}
|
||
// 6.4.2 CanonicalizeTimeZoneName (timeZone), step 3
|
||
if (timeZone === "UTC") {
|
||
return true;
|
||
}
|
||
// 6.4.2 CanonicalizeTimeZoneName (timeZone), step 3
|
||
if (timeZone === "Etc/UTC" || timeZone === "Etc/GMT") {
|
||
return false;
|
||
}
|
||
return zoneNamePattern.test(timeZone);
|
||
}
|