[INTL] Implement Intl.getCanonicalLocales
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Oct 2016 00:07:21 +0000 (00:07 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Oct 2016 00:07:21 +0000 (00:07 +0000)
https://bugs.webkit.org/show_bug.cgi?id=162768

Patch by Andy VanWagoner <thetalecrafter@gmail.com> on 2016-10-05
Reviewed by Benjamin Poulain.

Source/JavaScriptCore:

Implement Intl.getCanonicalLocales from ECMA 402 (3rd edition)
http://ecma-international.org/ecma-402/3.0/index.html#sec-intl.getcanonicallocales

Reuse canonicalizeLocaleList and copy the results into a new JSArray.

* runtime/IntlObject.cpp:
(JSC::IntlObject::finishCreation):
(JSC::intlObjectFuncGetCanonicalLocales):

LayoutTests:

* js/intl-expected.txt: Added tests for Intl.getCanonicalLocales
* js/script-tests/intl.js: Added test for Intl.getCanonicalLocales

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@206837 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/js/intl-expected.txt
LayoutTests/js/script-tests/intl.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/IntlObject.cpp

index 0c65ed6..f9cf25a 100644 (file)
@@ -1,3 +1,13 @@
+2016-10-05  Andy VanWagoner  <thetalecrafter@gmail.com>
+
+        [INTL] Implement Intl.getCanonicalLocales
+        https://bugs.webkit.org/show_bug.cgi?id=162768
+
+        Reviewed by Benjamin Poulain.
+
+        * js/intl-expected.txt: Added tests for Intl.getCanonicalLocales
+        * js/script-tests/intl.js: Added test for Intl.getCanonicalLocales
+
 2016-10-05  Ryan Haddad  <ryanhaddad@apple.com>
 
         Marking inspector/formatting/formatting-javascript.html as a flaky crash on mac-wk1.
index ac4b044..adee268 100644 (file)
@@ -12,6 +12,52 @@ PASS Intl() threw exception TypeError: Intl is not a function. (In 'Intl()', 'In
 PASS Object.keys(Intl).length is 0
 PASS delete Intl; is true
 PASS 'Intl' in global() is false
+PASS Intl.getCanonicalLocales.length is 1
+PASS Intl.getCanonicalLocales() is an instance of Array
+PASS Intl.getCanonicalLocales.call(null, 'en') is [ 'en' ]
+PASS Intl.getCanonicalLocales.call({}, 'en') is [ 'en' ]
+PASS Intl.getCanonicalLocales.call(1, 'en') is [ 'en' ]
+PASS Intl.getCanonicalLocales(9) is []
+PASS Intl.getCanonicalLocales('en') is [ 'en' ]
+PASS Intl.getCanonicalLocales({ length: 4, 1: 'en', 0: 'es', 3: 'de' }) is [ 'es', 'en', 'de' ]
+PASS Intl.getCanonicalLocales([ 'en', 'pt', 'en', 'es' ]) is [ 'en', 'pt', 'es' ]
+PASS Intl.getCanonicalLocales('En-laTn-us-variant2-variant1-1abc-U-ko-tRue-A-aa-aaa-x-RESERVED') is [ 'en-Latn-US-variant2-variant1-1abc-a-aa-aaa-u-ko-true-x-reserved' ]
+PASS Intl.getCanonicalLocales('no-bok') is [ 'nb' ]
+PASS Intl.getCanonicalLocales('X-some-thing') is [ 'x-some-thing' ]
+PASS Intl.getCanonicalLocales(Object.create(null, { length: { get() { throw Error('a') } } })) threw exception Error: a.
+PASS Intl.getCanonicalLocales(Object.create(null, { length: { value: 1 }, 0: { get() { throw Error('b') } } })) threw exception Error: b.
+PASS Intl.getCanonicalLocales([ { toString() { throw Error('c') } } ]) threw exception Error: c.
+PASS Intl.getCanonicalLocales([ 5 ]) threw exception TypeError: locale value must be a string or object.
+PASS Intl.getCanonicalLocales('') threw exception RangeError: invalid language tag: .
+PASS Intl.getCanonicalLocales('a') threw exception RangeError: invalid language tag: a.
+PASS Intl.getCanonicalLocales('abcdefghij') threw exception RangeError: invalid language tag: abcdefghij.
+PASS Intl.getCanonicalLocales('#$') threw exception RangeError: invalid language tag: #$.
+PASS Intl.getCanonicalLocales('en-@-abc') threw exception RangeError: invalid language tag: en-@-abc.
+PASS Intl.getCanonicalLocales('en-u') threw exception RangeError: invalid language tag: en-u.
+PASS Intl.getCanonicalLocales('en-u-kn-true-u-ko-true') threw exception RangeError: invalid language tag: en-u-kn-true-u-ko-true.
+PASS Intl.getCanonicalLocales('en-x') threw exception RangeError: invalid language tag: en-x.
+PASS Intl.getCanonicalLocales('en-*') threw exception RangeError: invalid language tag: en-*.
+PASS Intl.getCanonicalLocales('en-') threw exception RangeError: invalid language tag: en-.
+PASS Intl.getCanonicalLocales('en--US') threw exception RangeError: invalid language tag: en--US.
+PASS Intl.getCanonicalLocales('de') did not throw exception.
+PASS Intl.getCanonicalLocales('de-DE') did not throw exception.
+PASS Intl.getCanonicalLocales('DE-de') did not throw exception.
+PASS Intl.getCanonicalLocales('cmn') did not throw exception.
+PASS Intl.getCanonicalLocales('cmn-Hans') did not throw exception.
+PASS Intl.getCanonicalLocales('CMN-hANS') did not throw exception.
+PASS Intl.getCanonicalLocales('cmn-hans-cn') did not throw exception.
+PASS Intl.getCanonicalLocales('es-419') did not throw exception.
+PASS Intl.getCanonicalLocales('es-419-u-nu-latn-cu-bob') did not throw exception.
+PASS Intl.getCanonicalLocales('i-klingon') did not throw exception.
+PASS Intl.getCanonicalLocales('cmn-hans-cn-t-ca-u-ca-x-t-u') did not throw exception.
+PASS Intl.getCanonicalLocales('enochian-enochian') did not throw exception.
+PASS Intl.getCanonicalLocales('de-gregory-u-ca-gregory') did not throw exception.
+PASS Intl.getCanonicalLocales('aa-a-foo-x-a-foo-bar') did not throw exception.
+PASS Intl.getCanonicalLocales('x-en-US-12345') did not throw exception.
+PASS Intl.getCanonicalLocales('x-12345-12345-en-US') did not throw exception.
+PASS Intl.getCanonicalLocales('x-en-US-12345-12345') did not throw exception.
+PASS Intl.getCanonicalLocales('x-en-u-foo') did not throw exception.
+PASS Intl.getCanonicalLocales('x-en-u-foo-u-bar') did not throw exception.
 PASS successfullyParsed is true
 
 TEST COMPLETE
index bca620c..b93a175 100644 (file)
@@ -30,3 +30,71 @@ shouldBeFalse("'Intl' in global()");
 
 Intl = __Intl;
 
+// 8.2.1 Intl.getCanonicalLocales(locales)
+
+// The value of the length property of the getCanonicalLocales method is 1.
+shouldBe("Intl.getCanonicalLocales.length", "1");
+
+// Returns Locales
+shouldBeType("Intl.getCanonicalLocales()", "Array");
+// Doesn't care about `this`.
+shouldBe("Intl.getCanonicalLocales.call(null, 'en')", "[ 'en' ]");
+shouldBe("Intl.getCanonicalLocales.call({}, 'en')", "[ 'en' ]");
+shouldBe("Intl.getCanonicalLocales.call(1, 'en')", "[ 'en' ]");
+// Ignores non-object, non-string list.
+shouldBe("Intl.getCanonicalLocales(9)", "[]");
+// Makes an array of tags.
+shouldBe("Intl.getCanonicalLocales('en')", "[ 'en' ]");
+// Handles array-like objects with holes.
+shouldBe("Intl.getCanonicalLocales({ length: 4, 1: 'en', 0: 'es', 3: 'de' })", "[ 'es', 'en', 'de' ]");
+// Deduplicates tags.
+shouldBe("Intl.getCanonicalLocales([ 'en', 'pt', 'en', 'es' ])", "[ 'en', 'pt', 'es' ]");
+// Canonicalizes tags.
+shouldBe("Intl.getCanonicalLocales('En-laTn-us-variant2-variant1-1abc-U-ko-tRue-A-aa-aaa-x-RESERVED')", "[ 'en-Latn-US-variant2-variant1-1abc-a-aa-aaa-u-ko-true-x-reserved' ]");
+// Replaces outdated tags.
+shouldBe("Intl.getCanonicalLocales('no-bok')", "[ 'nb' ]");
+// Canonicalizes private tags.
+shouldBe("Intl.getCanonicalLocales('X-some-thing')", "[ 'x-some-thing' ]");
+// Throws on problems with length, get, or toString.
+shouldThrow("Intl.getCanonicalLocales(Object.create(null, { length: { get() { throw Error('a') } } }))", "'Error: a'");
+shouldThrow("Intl.getCanonicalLocales(Object.create(null, { length: { value: 1 }, 0: { get() { throw Error('b') } } }))", "'Error: b'");
+shouldThrow("Intl.getCanonicalLocales([ { toString() { throw Error('c') } } ])", "'Error: c'");
+// Throws on bad tags.
+shouldThrow("Intl.getCanonicalLocales([ 5 ])", "'TypeError: locale value must be a string or object'");
+shouldThrow("Intl.getCanonicalLocales('')", "'RangeError: invalid language tag: '");
+shouldThrow("Intl.getCanonicalLocales('a')", "'RangeError: invalid language tag: a'");
+shouldThrow("Intl.getCanonicalLocales('abcdefghij')", "'RangeError: invalid language tag: abcdefghij'");
+shouldThrow("Intl.getCanonicalLocales('#$')", "'RangeError: invalid language tag: #$'");
+shouldThrow("Intl.getCanonicalLocales('en-@-abc')", "'RangeError: invalid language tag: en-@-abc'");
+shouldThrow("Intl.getCanonicalLocales('en-u')", "'RangeError: invalid language tag: en-u'");
+shouldThrow("Intl.getCanonicalLocales('en-u-kn-true-u-ko-true')", "'RangeError: invalid language tag: en-u-kn-true-u-ko-true'");
+shouldThrow("Intl.getCanonicalLocales('en-x')", "'RangeError: invalid language tag: en-x'");
+shouldThrow("Intl.getCanonicalLocales('en-*')", "'RangeError: invalid language tag: en-*'");
+shouldThrow("Intl.getCanonicalLocales('en-')", "'RangeError: invalid language tag: en-'");
+shouldThrow("Intl.getCanonicalLocales('en--US')", "'RangeError: invalid language tag: en--US'");
+// Accepts valid tags
+var validLanguageTags = [
+    "de", // ISO 639 language code
+    "de-DE", // + ISO 3166-1 country code
+    "DE-de", // tags are case-insensitive
+    "cmn", // ISO 639 language code
+    "cmn-Hans", // + script code
+    "CMN-hANS", // tags are case-insensitive
+    "cmn-hans-cn", // + ISO 3166-1 country code
+    "es-419", // + UN M.49 region code
+    "es-419-u-nu-latn-cu-bob", // + Unicode locale extension sequence
+    "i-klingon", // grandfathered tag
+    "cmn-hans-cn-t-ca-u-ca-x-t-u", // singleton subtags can also be used as private use subtags
+    "enochian-enochian", // language and variant subtags may be the same
+    "de-gregory-u-ca-gregory", // variant and extension subtags may be the same
+    "aa-a-foo-x-a-foo-bar", // variant subtags can also be used as private use subtags
+    "x-en-US-12345", // anything goes in private use tags
+    "x-12345-12345-en-US",
+    "x-en-US-12345-12345",
+    "x-en-u-foo",
+    "x-en-u-foo-u-bar"
+];
+for (var validLanguageTag of validLanguageTags) {
+    shouldNotThrow("Intl.getCanonicalLocales('" + validLanguageTag + "')");
+}
+
index d56cb9f..c1f3699 100644 (file)
@@ -1,3 +1,19 @@
+2016-10-05  Andy VanWagoner  <thetalecrafter@gmail.com>
+
+        [INTL] Implement Intl.getCanonicalLocales
+        https://bugs.webkit.org/show_bug.cgi?id=162768
+
+        Reviewed by Benjamin Poulain.
+
+        Implement Intl.getCanonicalLocales from ECMA 402 (3rd edition)
+        http://ecma-international.org/ecma-402/3.0/index.html#sec-intl.getcanonicallocales
+
+        Reuse canonicalizeLocaleList and copy the results into a new JSArray.
+
+        * runtime/IntlObject.cpp:
+        (JSC::IntlObject::finishCreation):
+        (JSC::intlObjectFuncGetCanonicalLocales):
+
 2016-10-05  Michael Saboff  <msaboff@apple.com>
 
         Bad ASSERT in ClonedArguments::createByCopyingFrom()
index c3de7ed..a5f8ec5 100644 (file)
@@ -56,6 +56,8 @@ namespace JSC {
 
 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(IntlObject);
 
+static EncodedJSValue JSC_HOST_CALL intlObjectFuncGetCanonicalLocales(ExecState*);
+
 }
 
 namespace JSC {
@@ -110,6 +112,10 @@ void IntlObject::finishCreation(VM& vm, JSGlobalObject* globalObject)
     putDirectWithoutTransition(vm, vm.propertyNames->Collator, collatorConstructor, DontEnum);
     putDirectWithoutTransition(vm, vm.propertyNames->NumberFormat, numberFormatConstructor, DontEnum);
     putDirectWithoutTransition(vm, vm.propertyNames->DateTimeFormat, dateTimeFormatConstructor, DontEnum);
+
+    // 8.2 Function Properties of the Intl Object
+    // https://tc39.github.io/ecma402/#sec-function-properties-of-the-intl-object
+    putDirectNativeFunction(vm, globalObject, Identifier::fromString(&vm, "getCanonicalLocales"), 1, intlObjectFuncGetCanonicalLocales, NoIntrinsic, DontEnum);
 }
 
 Structure* IntlObject::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
@@ -1010,6 +1016,33 @@ Vector<String> numberingSystemsForLocale(const String& locale)
     return numberingSystems;
 }
 
+EncodedJSValue JSC_HOST_CALL intlObjectFuncGetCanonicalLocales(ExecState* state)
+{
+    VM& vm = state->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+    // https://tc39.github.io/ecma402/#sec-intl.getcanonicallocales
+    // 8.2.1 Intl.getCanonicalLocales(locales) (ECMA-402 4.0)
+
+    // 1. Let ll be ? CanonicalizeLocaleList(locales).
+    Vector<String> localeList = canonicalizeLocaleList(*state, state->argument(0));
+    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+    // 2. Return CreateArrayFromList(ll).
+    JSGlobalObject* globalObject = state->callee()->globalObject();
+    JSArray* localeArray = JSArray::tryCreateUninitialized(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), localeList.size());
+    if (!localeArray) {
+        throwOutOfMemoryError(state, scope);
+        return encodedJSValue();
+    }
+
+    auto length = localeList.size();
+    for (size_t i = 0; i < length; ++i) {
+        localeArray->initializeIndex(vm, i, jsString(state, localeList[i]));
+        RETURN_IF_EXCEPTION(scope, encodedJSValue());
+    }
+    return JSValue::encode(localeArray);
+}
+
 } // namespace JSC
 
 #endif // ENABLE(INTL)