[JSC] Intl.Collator should set usage:"search" option through ICU locale
https://bugs.webkit.org/show_bug.cgi?id=213869

Reviewed by Ross Kirsling.

JSTests:

* stress/intl-collator-co-extension.js: Added.
(shouldBe):
(shouldBeArray):
(explicitTrueBeforeICU67):
* test262/expectations.yaml:

Source/JavaScriptCore:

Intl.Collator has usage:"search" option, and it affects on collation. However, UCollator does not have an interface to set this collation option,
and only way to configure UCollator is setting "-u-co-search" unicode extension to passed locale string. This patch adds "-u-co-search" unicode
extension if Usage::Search is specified.

* runtime/IntlCollator.cpp:
(JSC::IntlCollator::initializeCollator):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@263833 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog
index a63597f..1d9481d 100644
--- a/JSTests/ChangeLog
+++ b/JSTests/ChangeLog
@@ -1,3 +1,16 @@
+2020-07-01  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Intl.Collator should set usage:"search" option through ICU locale
+        https://bugs.webkit.org/show_bug.cgi?id=213869
+
+        Reviewed by Ross Kirsling.
+
+        * stress/intl-collator-co-extension.js: Added.
+        (shouldBe):
+        (shouldBeArray):
+        (explicitTrueBeforeICU67):
+        * test262/expectations.yaml:
+
 2020-06-26  Yusuke Suzuki  <ysuzuki@apple.com>
 
         Update test262
diff --git a/JSTests/stress/intl-collator-co-extension.js b/JSTests/stress/intl-collator-co-extension.js
new file mode 100644
index 0000000..c148784
--- /dev/null
+++ b/JSTests/stress/intl-collator-co-extension.js
@@ -0,0 +1,49 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function shouldBeArray(actual, expected) {
+    shouldBe(actual.length, expected.length);
+    for (var i = 0; i < expected.length; ++i) {
+        try {
+            shouldBe(actual[i], expected[i]);
+        } catch(e) {
+            print(JSON.stringify(actual));
+            throw e;
+        }
+    }
+}
+
+function explicitTrueBeforeICU67() {
+    return $vm.icuVersion() < 67 ? '-true' : '';
+}
+
+shouldBeArray(["AE", "\u00C4"].sort(new Intl.Collator("de", {usage: "sort"}).compare), ["\u00C4", "AE"]);
+shouldBeArray(["AE", "\u00C4"].sort(new Intl.Collator("de", {usage: "search"}).compare), ["AE", "\u00C4"]);
+shouldBe(new Intl.Collator("de", {usage: "sort"}).resolvedOptions().locale, "de");
+shouldBe(new Intl.Collator("de", {usage: "search"}).resolvedOptions().locale, "de");
+shouldBeArray(["2", "10"].sort(new Intl.Collator("de", {usage: "sort"}).compare), ["10", "2"]);
+shouldBeArray(["2", "10"].sort(new Intl.Collator("de", {usage: "search"}).compare), ["10", "2"]);
+
+shouldBeArray(["AE", "\u00C4"].sort(new Intl.Collator("de-u-co-search", {usage: "sort"}).compare), ["\u00C4", "AE"]);
+shouldBeArray(["AE", "\u00C4"].sort(new Intl.Collator("de-u-co-sort", {usage: "search"}).compare), ["AE", "\u00C4"]);
+shouldBe(new Intl.Collator("de-u-co-search", {usage: "sort"}).resolvedOptions().locale, "de");
+shouldBe(new Intl.Collator("de-u-co-sort", {usage: "search"}).resolvedOptions().locale, "de");
+
+shouldBeArray(["AE", "\u00C4"].sort(new Intl.Collator("de-u-kn", {usage: "sort"}).compare), ["\u00C4", "AE"]);
+shouldBeArray(["AE", "\u00C4"].sort(new Intl.Collator("de-u-kn", {usage: "search"}).compare), ["AE", "\u00C4"]);
+shouldBeArray(["2", "10"].sort(new Intl.Collator("de-u-kn", {usage: "sort"}).compare), ["2", "10"]);
+shouldBeArray(["2", "10"].sort(new Intl.Collator("de-u-kn", {usage: "search"}).compare), ["2", "10"]);
+shouldBeArray(["2", "10"].sort(new Intl.Collator("de-U-kn", {usage: "sort"}).compare), ["2", "10"]);
+shouldBeArray(["2", "10"].sort(new Intl.Collator("de-U-kn-x-0", {usage: "search"}).compare), ["2", "10"]);
+
+shouldBe(new Intl.Collator("en-US-x-twain", {usage: "search"}).resolvedOptions().locale, "en-US");
+
+shouldBe(new Intl.Collator("de-u-kn", {usage: "sort"}).resolvedOptions().locale, "de-u-kn" + explicitTrueBeforeICU67());
+shouldBe(new Intl.Collator("de-u-kn", {usage: "search"}).resolvedOptions().locale, "de-u-kn" + explicitTrueBeforeICU67());
+
+shouldBeArray(["a", "ae", "ä", "æ"].sort(new Intl.Collator("de-u-co-phonebk").compare), ["a", "ae", "ä", "æ"]);
+shouldBeArray(["a", "ae", "ä", "æ"].sort(new Intl.Collator("de").compare), ["a", "ä", "ae", "æ"]);
+shouldBeArray(["a", "ae", "ä", "æ"].sort(new Intl.Collator("de-u-co-phonebk", { usage: 'search' }).compare), ["a", "ae", "ä", "æ"]);
+shouldBeArray(["a", "ae", "ä", "æ"].sort(new Intl.Collator("de", { usage: 'search' }).compare), ["a", "ae", "ä", "æ"]);
diff --git a/JSTests/test262/expectations.yaml b/JSTests/test262/expectations.yaml
index f8b9dd9..dd77a7c 100644
--- a/JSTests/test262/expectations.yaml
+++ b/JSTests/test262/expectations.yaml
@@ -1605,9 +1605,6 @@
 test/intl402/Collator/missing-unicode-ext-value-defaults-to-true.js:
   default: "Test262Error: \"kn-true\" is returned in locale, but shouldn't be. Expected SameValue(«7», «-1») to be true"
   strict mode: "Test262Error: \"kn-true\" is returned in locale, but shouldn't be. Expected SameValue(«7», «-1») to be true"
-test/intl402/Collator/usage-de.js:
-  default: 'Test262Error: Expected [Ä, AE] and [AE, Ä] to have the same contents. search'
-  strict mode: 'Test262Error: Expected [Ä, AE] and [AE, Ä] to have the same contents. search'
 test/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-default.js:
   default: 'Test262Error: Expected SameValue(«h24», «h23») to be true'
   strict mode: 'Test262Error: Expected SameValue(«h24», «h23») to be true'
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index c5db482..517bc8b 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,17 @@
+2020-07-01  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Intl.Collator should set usage:"search" option through ICU locale
+        https://bugs.webkit.org/show_bug.cgi?id=213869
+
+        Reviewed by Ross Kirsling.
+
+        Intl.Collator has usage:"search" option, and it affects on collation. However, UCollator does not have an interface to set this collation option,
+        and only way to configure UCollator is setting "-u-co-search" unicode extension to passed locale string. This patch adds "-u-co-search" unicode
+        extension if Usage::Search is specified.
+
+        * runtime/IntlCollator.cpp:
+        (JSC::IntlCollator::initializeCollator):
+
 2020-07-01  Keith Miller  <keith_miller@apple.com>
 
         Rename zeroExtend32ToPtr to zeroExtend32ToWord
diff --git a/Source/JavaScriptCore/runtime/IntlCollator.cpp b/Source/JavaScriptCore/runtime/IntlCollator.cpp
index f00a66b..bc74977 100644
--- a/Source/JavaScriptCore/runtime/IntlCollator.cpp
+++ b/Source/JavaScriptCore/runtime/IntlCollator.cpp
@@ -43,6 +43,7 @@
 constexpr size_t collationIndex = 0;
 constexpr size_t caseFirstIndex = 1;
 constexpr size_t numericIndex = 2;
+constexpr bool verbose = false;
 }
 
 void IntlCollator::UCollatorDeleter::operator()(UCollator* collator) const
@@ -240,8 +241,23 @@
     RETURN_IF_EXCEPTION(scope, void());
     m_ignorePunctuation = (ignorePunctuation == TriState::True);
 
+    // UCollator does not offer an option to configure "usage" via ucol_setAttribute. So we need to pass this option via locale.
+    CString dataLocaleWithExtensions;
+    switch (m_usage) {
+    case Usage::Sort:
+        dataLocaleWithExtensions = m_locale.utf8();
+        break;
+    case Usage::Search:
+        // searchLocaleData filters out "co" unicode extension. However, we need to pass "co" to ICU when Usage::Search is specified.
+        // So we need to pass "co" unicode extension through locale. Since the other relevant extensions are handled via ucol_setAttribute,
+        // we can just use dataLocale
+        dataLocaleWithExtensions = makeString(result.get("dataLocale"_s), "-u-co-search").utf8();
+        break;
+    }
+    dataLogLnIf(IntlCollatorInternal::verbose, "dataLocaleWithExtensions:(", dataLocaleWithExtensions, ")");
+
     UErrorCode status = U_ZERO_ERROR;
-    m_collator = std::unique_ptr<UCollator, UCollatorDeleter>(ucol_open(m_locale.utf8().data(), &status));
+    m_collator = std::unique_ptr<UCollator, UCollatorDeleter>(ucol_open(dataLocaleWithExtensions.data(), &status));
     if (U_FAILURE(status)) {
         throwTypeError(globalObject, scope, "failed to initialize Collator"_s);
         return;