зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1433306 - Part 2: Implement Intl.ListFormat stage 3 proposal. r=jwalden
"disjunction" and "unit" types aren't yet supported, because ICU doesn't provide a C-API for this functionality. "short" and "narrow" styles aren't supported for the same reason. Differential Revision: https://phabricator.services.mozilla.com/D40437 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
c113d26df5
Коммит
0b138ce1ee
|
@ -100,6 +100,7 @@ included_inclnames_to_ignore = set([
|
|||
'unicode/uenum.h', # ICU
|
||||
'unicode/ufieldpositer.h', # ICU
|
||||
'unicode/uformattedvalue.h', # ICU
|
||||
'unicode/ulistformatter.h', # ICU
|
||||
'unicode/uloc.h', # ICU
|
||||
'unicode/unistr.h', # ICU
|
||||
'unicode/unorm2.h', # ICU
|
||||
|
|
|
@ -1321,6 +1321,7 @@ if CONFIG['MOZ_SYSTEM_ICU']:
|
|||
'unicode/udata.h',
|
||||
'unicode/udatpg.h',
|
||||
'unicode/udisplaycontext.h',
|
||||
'unicode/ulistformatter.h',
|
||||
'unicode/uenum.h',
|
||||
'unicode/uformattedvalue.h',
|
||||
'unicode/unistr.h',
|
||||
|
|
|
@ -104,6 +104,7 @@
|
|||
REAL_IF_INTL(Collator, InitViaClassSpec, OCLASP(Collator)) \
|
||||
REAL_IF_INTL(DateTimeFormat, InitViaClassSpec, OCLASP(DateTimeFormat)) \
|
||||
REAL_IF_INTL(Locale, InitViaClassSpec, OCLASP(Locale)) \
|
||||
REAL_IF_INTL(ListFormat, InitViaClassSpec, OCLASP(ListFormat)) \
|
||||
REAL_IF_INTL(NumberFormat, InitViaClassSpec, OCLASP(NumberFormat)) \
|
||||
REAL_IF_INTL(PluralRules, InitViaClassSpec, OCLASP(PluralRules)) \
|
||||
REAL_IF_INTL(RelativeTimeFormat, InitViaClassSpec, \
|
||||
|
|
|
@ -706,6 +706,7 @@ function initializeIntlObject(obj, type, lazyData) {
|
|||
assert(IsObject(obj), "Non-object passed to initializeIntlObject");
|
||||
assert((type === "Collator" && GuardToCollator(obj) !== null) ||
|
||||
(type === "DateTimeFormat" && GuardToDateTimeFormat(obj) !== null) ||
|
||||
(type === "ListFormat" && GuardToListFormat(obj) !== null) ||
|
||||
(type === "NumberFormat" && GuardToNumberFormat(obj) !== null) ||
|
||||
(type === "PluralRules" && GuardToPluralRules(obj) !== null) ||
|
||||
(type === "RelativeTimeFormat" && GuardToRelativeTimeFormat(obj) !== null),
|
||||
|
@ -772,8 +773,11 @@ function maybeInternalProperties(internals) {
|
|||
*/
|
||||
function getIntlObjectInternals(obj) {
|
||||
assert(IsObject(obj), "getIntlObjectInternals called with non-Object");
|
||||
assert(GuardToCollator(obj) !== null || GuardToDateTimeFormat(obj) !== null ||
|
||||
GuardToNumberFormat(obj) !== null || GuardToPluralRules(obj) !== null ||
|
||||
assert(GuardToCollator(obj) !== null ||
|
||||
GuardToDateTimeFormat(obj) !== null ||
|
||||
GuardToListFormat(obj) !== null ||
|
||||
GuardToNumberFormat(obj) !== null ||
|
||||
GuardToPluralRules(obj) !== null ||
|
||||
GuardToRelativeTimeFormat(obj) !== null,
|
||||
"getIntlObjectInternals called with non-Intl object");
|
||||
|
||||
|
@ -783,6 +787,7 @@ function getIntlObjectInternals(obj) {
|
|||
assert(hasOwn("type", internals), "missing type");
|
||||
assert((internals.type === "Collator" && GuardToCollator(obj) !== null) ||
|
||||
(internals.type === "DateTimeFormat" && GuardToDateTimeFormat(obj) !== null) ||
|
||||
(internals.type === "ListFormat" && GuardToListFormat(obj) !== null) ||
|
||||
(internals.type === "NumberFormat" && GuardToNumberFormat(obj) !== null) ||
|
||||
(internals.type === "PluralRules" && GuardToPluralRules(obj) !== null) ||
|
||||
(internals.type === "RelativeTimeFormat" && GuardToRelativeTimeFormat(obj) !== null),
|
||||
|
@ -811,6 +816,8 @@ function getInternals(obj) {
|
|||
internalProps = resolveCollatorInternals(internals.lazyData);
|
||||
else if (type === "DateTimeFormat")
|
||||
internalProps = resolveDateTimeFormatInternals(internals.lazyData);
|
||||
else if (type === "ListFormat")
|
||||
internalProps = resolveListFormatInternals(internals.lazyData);
|
||||
else if (type === "NumberFormat")
|
||||
internalProps = resolveNumberFormatInternals(internals.lazyData);
|
||||
else if (type === "PluralRules")
|
||||
|
|
|
@ -620,6 +620,8 @@ bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) {
|
|||
kind = SupportedLocaleKind::Collator;
|
||||
} else if (StringEqualsLiteral(typeStr, "DateTimeFormat")) {
|
||||
kind = SupportedLocaleKind::DateTimeFormat;
|
||||
} else if (StringEqualsLiteral(typeStr, "ListFormat")) {
|
||||
kind = SupportedLocaleKind::ListFormat;
|
||||
} else if (StringEqualsLiteral(typeStr, "NumberFormat")) {
|
||||
kind = SupportedLocaleKind::NumberFormat;
|
||||
} else if (StringEqualsLiteral(typeStr, "PluralRules")) {
|
||||
|
@ -754,12 +756,12 @@ bool js::intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc,
|
|||
// That implies we must ignore any candidate which isn't supported by all Intl
|
||||
// service constructors.
|
||||
//
|
||||
// Note: We don't test the supported locales of either Intl.PluralRules or
|
||||
// Intl.RelativeTimeFormat, because ICU doesn't provide the necessary API to
|
||||
// return actual set of supported locales for these constructors. Instead it
|
||||
// returns the complete set of available locales for ULocale, which is a
|
||||
// superset of the locales supported by Collator, NumberFormat, and
|
||||
// DateTimeFormat.
|
||||
// Note: We don't test the supported locales of either Intl.ListFormat,
|
||||
// Intl.PluralRules, Intl.RelativeTimeFormat, because ICU doesn't provide the
|
||||
// necessary API to return actual set of supported locales for these
|
||||
// constructors. Instead it returns the complete set of available locales for
|
||||
// ULocale, which is a superset of the locales supported by Collator,
|
||||
// NumberFormat, and DateTimeFormat.
|
||||
bool isSupported = true;
|
||||
for (auto kind :
|
||||
{SupportedLocaleKind::Collator, SupportedLocaleKind::DateTimeFormat,
|
||||
|
|
|
@ -0,0 +1,488 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "builtin/Array.h"
|
||||
#include "builtin/intl/CommonFunctions.h"
|
||||
#include "builtin/intl/ScopedICUObject.h"
|
||||
#include "gc/FreeOp.h"
|
||||
#include "js/Utility.h"
|
||||
#include "js/Vector.h"
|
||||
#include "unicode/uformattedvalue.h"
|
||||
#include "unicode/ulistformatter.h"
|
||||
#include "unicode/utypes.h"
|
||||
#include "vm/JSContext.h"
|
||||
#include "vm/SelfHosting.h"
|
||||
#include "vm/StringType.h"
|
||||
|
||||
#include "vm/JSObject-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
#include "vm/ObjectOperations-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
using mozilla::AssertedCast;
|
||||
|
||||
using js::intl::CallICU;
|
||||
using js::intl::IcuLocale;
|
||||
|
||||
const JSClassOps ListFormatObject::classOps_ = {nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* newEnumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
ListFormatObject::finalize};
|
||||
const JSClass ListFormatObject::class_ = {
|
||||
js_Object_str,
|
||||
JSCLASS_HAS_RESERVED_SLOTS(ListFormatObject::SLOT_COUNT) |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_ListFormat) |
|
||||
JSCLASS_FOREGROUND_FINALIZE,
|
||||
&ListFormatObject::classOps_, &ListFormatObject::classSpec_};
|
||||
|
||||
const JSClass& ListFormatObject::protoClass_ = PlainObject::class_;
|
||||
|
||||
static bool listFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setString(cx->names().ListFormat);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSFunctionSpec listFormat_static_methods[] = {
|
||||
JS_SELF_HOSTED_FN("supportedLocalesOf",
|
||||
"Intl_ListFormat_supportedLocalesOf", 1, 0),
|
||||
JS_FS_END};
|
||||
|
||||
static const JSFunctionSpec listFormat_methods[] = {
|
||||
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_ListFormat_resolvedOptions", 0,
|
||||
0),
|
||||
JS_SELF_HOSTED_FN("format", "Intl_ListFormat_format", 1, 0),
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
JS_SELF_HOSTED_FN("formatToParts", "Intl_ListFormat_formatToParts", 1, 0),
|
||||
#endif
|
||||
JS_FN(js_toSource_str, listFormat_toSource, 0, 0), JS_FS_END};
|
||||
|
||||
static const JSPropertySpec listFormat_properties[] = {
|
||||
JS_STRING_SYM_PS(toStringTag, "Intl.ListFormat", JSPROP_READONLY),
|
||||
JS_PS_END};
|
||||
|
||||
static bool ListFormat(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
const ClassSpec ListFormatObject::classSpec_ = {
|
||||
GenericCreateConstructor<ListFormat, 0, gc::AllocKind::FUNCTION>,
|
||||
GenericCreatePrototype<ListFormatObject>,
|
||||
listFormat_static_methods,
|
||||
nullptr,
|
||||
listFormat_methods,
|
||||
listFormat_properties,
|
||||
nullptr,
|
||||
ClassSpec::DontDefineConstructor};
|
||||
|
||||
/**
|
||||
* Intl.ListFormat([ locales [, options]])
|
||||
*/
|
||||
static bool ListFormat(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1.
|
||||
if (!ThrowIfNotConstructing(cx, args, "Intl.ListFormat")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
|
||||
RootedObject proto(cx);
|
||||
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ListFormat,
|
||||
&proto)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ListFormatObject*> listFormat(
|
||||
cx, NewObjectWithClassProto<ListFormatObject>(cx, proto));
|
||||
if (!listFormat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleValue locales = args.get(0);
|
||||
HandleValue options = args.get(1);
|
||||
|
||||
// Step 3.
|
||||
if (!intl::InitializeObject(cx, listFormat, cx->names().InitializeListFormat,
|
||||
locales, options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*listFormat);
|
||||
return true;
|
||||
}
|
||||
|
||||
void js::ListFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
|
||||
MOZ_ASSERT(fop->onMainThread());
|
||||
|
||||
if (UListFormatter* lf = obj->as<ListFormatObject>().getListFormatter()) {
|
||||
ulistfmt_close(lf);
|
||||
}
|
||||
}
|
||||
|
||||
bool js::AddListFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl) {
|
||||
JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, JSProto_ListFormat);
|
||||
if (!ctor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue ctorValue(cx, ObjectValue(*ctor));
|
||||
return DefineDataProperty(cx, intl, cx->names().ListFormat, ctorValue, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new UListFormatter with the locale and list formatting options
|
||||
* of the given ListFormat.
|
||||
*/
|
||||
static UListFormatter* NewUListFormatter(JSContext* cx,
|
||||
Handle<ListFormatObject*> listFormat) {
|
||||
RootedObject internals(cx, intl::GetInternalsObject(cx, listFormat));
|
||||
if (!internals) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedValue value(cx);
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
UniqueChars locale = intl::EncodeLocale(cx, value.toString());
|
||||
if (!locale) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
enum class ListFormatType { Conjunction, Disjunction, Unit };
|
||||
|
||||
ListFormatType type;
|
||||
if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
{
|
||||
JSLinearString* strType = value.toString()->ensureLinear(cx);
|
||||
if (!strType) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (StringEqualsLiteral(strType, "conjunction")) {
|
||||
type = ListFormatType::Conjunction;
|
||||
} else if (StringEqualsLiteral(strType, "disjunction")) {
|
||||
type = ListFormatType::Disjunction;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsLiteral(strType, "unit"));
|
||||
type = ListFormatType::Unit;
|
||||
}
|
||||
}
|
||||
|
||||
enum class ListFormatStyle { Long, Short, Narrow };
|
||||
|
||||
ListFormatStyle style;
|
||||
if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
{
|
||||
JSLinearString* strStyle = value.toString()->ensureLinear(cx);
|
||||
if (!strStyle) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (StringEqualsLiteral(strStyle, "long")) {
|
||||
style = ListFormatStyle::Long;
|
||||
} else if (StringEqualsLiteral(strStyle, "short")) {
|
||||
style = ListFormatStyle::Short;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsLiteral(strStyle, "narrow"));
|
||||
style = ListFormatStyle::Narrow;
|
||||
}
|
||||
}
|
||||
|
||||
// We're currently only supporting "conjunctive-long" list formatters due to
|
||||
// missing ICU APIs: https://unicode-org.atlassian.net/browse/ICU-12863
|
||||
MOZ_ASSERT(type == ListFormatType::Conjunction);
|
||||
MOZ_ASSERT(style == ListFormatStyle::Long);
|
||||
|
||||
mozilla::Unused << type;
|
||||
mozilla::Unused << style;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UListFormatter* lf = ulistfmt_open(IcuLocale(locale.get()), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
return lf;
|
||||
}
|
||||
|
||||
static constexpr size_t DEFAULT_LIST_LENGTH = 8;
|
||||
|
||||
using ListFormatStringVector = Vector<UniqueTwoByteChars, DEFAULT_LIST_LENGTH>;
|
||||
using ListFormatStringLengthVector = Vector<int32_t, DEFAULT_LIST_LENGTH>;
|
||||
|
||||
static_assert(sizeof(UniqueTwoByteChars) == sizeof(char16_t*),
|
||||
"UniqueTwoByteChars are stored efficiently and are held in "
|
||||
"continuous memory");
|
||||
|
||||
/**
|
||||
* FormatList ( listFormat, list )
|
||||
*/
|
||||
static bool FormatList(JSContext* cx, UListFormatter* lf,
|
||||
const ListFormatStringVector& strings,
|
||||
const ListFormatStringLengthVector& stringLengths,
|
||||
MutableHandleValue result) {
|
||||
MOZ_ASSERT(strings.length() == stringLengths.length());
|
||||
MOZ_ASSERT(strings.length() <= INT32_MAX);
|
||||
|
||||
JSString* str = intl::CallICU(cx, [lf, &strings, &stringLengths](
|
||||
UChar* chars, int32_t size,
|
||||
UErrorCode* status) {
|
||||
return ulistfmt_format(
|
||||
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
|
||||
stringLengths.begin(), int32_t(strings.length()), chars, size, status);
|
||||
});
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result.setString(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
static JSString* FormattedValueToString(JSContext* cx,
|
||||
const UFormattedValue* formattedValue) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
int32_t strLength;
|
||||
const char16_t* str = ufmtval_getString(formattedValue, &strLength, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return NewStringCopyN<CanGC>(cx, str, AssertedCast<uint32_t>(strLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* FormatListToParts ( listFormat, list )
|
||||
*/
|
||||
static bool FormatListToParts(JSContext* cx, UListFormatter* lf,
|
||||
const ListFormatStringVector& strings,
|
||||
const ListFormatStringLengthVector& stringLengths,
|
||||
MutableHandleValue result) {
|
||||
MOZ_ASSERT(strings.length() == stringLengths.length());
|
||||
MOZ_ASSERT(strings.length() <= INT32_MAX);
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UFormattedList* formatted = ulistfmt_openResult(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UFormattedList, ulistfmt_closeResult> toClose(formatted);
|
||||
|
||||
ulistfmt_formatStringsToResult(
|
||||
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
|
||||
stringLengths.begin(), int32_t(strings.length()), formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
const UFormattedValue* formattedValue =
|
||||
ulistfmt_resultAsValue(formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedString overallResult(cx, FormattedValueToString(cx, formattedValue));
|
||||
if (!overallResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
|
||||
if (!partsArray) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*;
|
||||
|
||||
size_t lastEndIndex = 0;
|
||||
RootedObject singlePart(cx);
|
||||
RootedValue val(cx);
|
||||
|
||||
auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
|
||||
singlePart = NewBuiltinClassInstance<PlainObject>(cx);
|
||||
if (!singlePart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val = StringValue(cx->names().*type);
|
||||
if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSLinearString* partSubstr = NewDependentString(
|
||||
cx, overallResult, beginIndex, endIndex - beginIndex);
|
||||
if (!partSubstr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val = StringValue(partSubstr);
|
||||
if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lastEndIndex = endIndex;
|
||||
return true;
|
||||
};
|
||||
|
||||
UConstrainedFieldPosition* fpos = ucfpos_open(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
|
||||
|
||||
// We're only interested in ULISTFMT_ELEMENT_FIELD fields.
|
||||
ucfpos_constrainField(fpos, UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD,
|
||||
&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
if (!hasMore) {
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t beginIndexInt, endIndexInt;
|
||||
ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(beginIndexInt >= 0);
|
||||
MOZ_ASSERT(endIndexInt >= 0);
|
||||
MOZ_ASSERT(beginIndexInt <= endIndexInt,
|
||||
"field iterator returning invalid range");
|
||||
|
||||
size_t beginIndex = size_t(beginIndexInt);
|
||||
size_t endIndex = size_t(endIndexInt);
|
||||
|
||||
// Indices are guaranteed to be returned in order (from left to right).
|
||||
MOZ_ASSERT(lastEndIndex <= beginIndex,
|
||||
"field iteration didn't return fields in order start to "
|
||||
"finish as expected");
|
||||
|
||||
if (lastEndIndex < beginIndex) {
|
||||
if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AppendPart(&JSAtomState::element, beginIndex, endIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Append any final literal.
|
||||
if (lastEndIndex < overallResult->length()) {
|
||||
if (!AppendPart(&JSAtomState::literal, lastEndIndex,
|
||||
overallResult->length())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result.setObject(*partsArray);
|
||||
return true;
|
||||
}
|
||||
#endif // U_HIDE_DRAFT_API
|
||||
|
||||
bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 3);
|
||||
|
||||
Rooted<ListFormatObject*> listFormat(
|
||||
cx, &args[0].toObject().as<ListFormatObject>());
|
||||
|
||||
bool formatToParts = args[2].toBoolean();
|
||||
|
||||
// Obtain a cached UListFormatter object.
|
||||
UListFormatter* lf = listFormat->getListFormatter();
|
||||
if (!lf) {
|
||||
lf = NewUListFormatter(cx, listFormat);
|
||||
if (!lf) {
|
||||
return false;
|
||||
}
|
||||
listFormat->setListFormatter(lf);
|
||||
}
|
||||
|
||||
// Collect all strings and their lengths.
|
||||
ListFormatStringVector strings(cx);
|
||||
ListFormatStringLengthVector stringLengths(cx);
|
||||
|
||||
RootedArrayObject list(cx, &args[1].toObject().as<ArrayObject>());
|
||||
RootedValue value(cx);
|
||||
for (uint32_t i = 0; i < list->length(); i++) {
|
||||
if (!GetElement(cx, list, list, i, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSLinearString* linear = value.toString()->ensureLinear(cx);
|
||||
if (!linear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t linearLength = linear->length();
|
||||
if (!stringLengths.append(linearLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UniqueTwoByteChars chars = cx->make_pod_array<char16_t>(linearLength);
|
||||
if (!chars) {
|
||||
return false;
|
||||
}
|
||||
CopyChars(chars.get(), *linear);
|
||||
|
||||
if (!strings.append(std::move(chars))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the UListFormatter to actually format the strings.
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
if (formatToParts) {
|
||||
return FormatListToParts(cx, lf, strings, stringLengths, args.rval());
|
||||
}
|
||||
#else
|
||||
MOZ_ASSERT(!formatToParts);
|
||||
#endif
|
||||
|
||||
return FormatList(cx, lf, strings, stringLengths, args.rval());
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef builtin_intl_ListFormat_h
|
||||
#define builtin_intl_ListFormat_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "js/Class.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
class JSFreeOp;
|
||||
struct UListFormatter;
|
||||
|
||||
namespace js {
|
||||
|
||||
class ListFormatObject : public NativeObject {
|
||||
public:
|
||||
static const JSClass class_;
|
||||
static const JSClass& protoClass_;
|
||||
|
||||
static constexpr uint32_t INTERNALS_SLOT = 0;
|
||||
static constexpr uint32_t ULIST_FORMATTER_SLOT = 1;
|
||||
static constexpr uint32_t SLOT_COUNT = 2;
|
||||
|
||||
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
|
||||
"INTERNALS_SLOT must match self-hosting define for internals "
|
||||
"object slot");
|
||||
|
||||
UListFormatter* getListFormatter() const {
|
||||
const auto& slot = getFixedSlot(ULIST_FORMATTER_SLOT);
|
||||
if (slot.isUndefined()) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<UListFormatter*>(slot.toPrivate());
|
||||
}
|
||||
|
||||
void setListFormatter(UListFormatter* formatter) {
|
||||
setFixedSlot(ULIST_FORMATTER_SLOT, PrivateValue(formatter));
|
||||
}
|
||||
|
||||
private:
|
||||
static const JSClassOps classOps_;
|
||||
static const ClassSpec classSpec_;
|
||||
|
||||
static void finalize(JSFreeOp* fop, JSObject* obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string representing the array of string values |list| according to
|
||||
* the effective locale and the formatting options of the given ListFormat.
|
||||
*
|
||||
* Usage: formatted = intl_FormatList(listFormat, list, formatToParts)
|
||||
*/
|
||||
extern MOZ_MUST_USE bool intl_FormatList(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_intl_ListFormat_h */
|
|
@ -0,0 +1,262 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* ListFormat internal properties.
|
||||
*/
|
||||
var listFormatInternalProperties = {
|
||||
localeData: function() // eslint-disable-line object-shorthand
|
||||
{
|
||||
// ListFormat don't support any extension keys.
|
||||
return {};
|
||||
},
|
||||
relevantExtensionKeys: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Intl.ListFormat ( [ locales [ , options ] ] )
|
||||
*
|
||||
* Compute an internal properties object from |lazyListFormatData|.
|
||||
*/
|
||||
function resolveListFormatInternals(lazyListFormatData) {
|
||||
assert(IsObject(lazyListFormatData), "lazy data not an object?");
|
||||
|
||||
var internalProps = std_Object_create(null);
|
||||
|
||||
var ListFormat = listFormatInternalProperties;
|
||||
|
||||
// Compute effective locale.
|
||||
|
||||
// Step 9.
|
||||
var localeData = ListFormat.localeData;
|
||||
|
||||
// Step 10.
|
||||
var r = ResolveLocale("ListFormat",
|
||||
lazyListFormatData.requestedLocales,
|
||||
lazyListFormatData.opt,
|
||||
ListFormat.relevantExtensionKeys,
|
||||
localeData);
|
||||
|
||||
// Step 11.
|
||||
internalProps.locale = r.locale;
|
||||
|
||||
// Step 13.
|
||||
internalProps.type = lazyListFormatData.type;
|
||||
|
||||
// Step 15.
|
||||
internalProps.style = lazyListFormatData.style;
|
||||
|
||||
// Steps 16-23 (not applicable in our implementation).
|
||||
|
||||
// The caller is responsible for associating |internalProps| with the right
|
||||
// object using |setInternalProperties|.
|
||||
return internalProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing the ListFormat internal properties of |obj|.
|
||||
*/
|
||||
function getListFormatInternals(obj) {
|
||||
assert(IsObject(obj), "getListFormatInternals called with non-object");
|
||||
assert(GuardToListFormat(obj) !== null, "getListFormatInternals called with non-ListFormat");
|
||||
|
||||
var internals = getIntlObjectInternals(obj);
|
||||
assert(internals.type === "ListFormat", "bad type escaped getIntlObjectInternals");
|
||||
|
||||
// If internal properties have already been computed, use them.
|
||||
var internalProps = maybeInternalProperties(internals);
|
||||
if (internalProps)
|
||||
return internalProps;
|
||||
|
||||
// Otherwise it's time to fully create them.
|
||||
internalProps = resolveListFormatInternals(internals.lazyData);
|
||||
setInternalProperties(internals, internalProps);
|
||||
return internalProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intl.ListFormat ( [ locales [ , options ] ] )
|
||||
*
|
||||
* Initializes an object as a ListFormat.
|
||||
*
|
||||
* This method is complicated a moderate bit by its implementing initialization
|
||||
* as a *lazy* concept. Everything that must happen now, does -- but we defer
|
||||
* all the work we can until the object is actually used as a ListFormat.
|
||||
* This later work occurs in |resolveListFormatInternals|; steps not noted
|
||||
* here occur there.
|
||||
*/
|
||||
function InitializeListFormat(listFormat, locales, options) {
|
||||
assert(IsObject(listFormat), "InitializeListFormat called with non-object");
|
||||
assert(GuardToListFormat(listFormat) !== null, "InitializeListFormat called with non-ListFormat");
|
||||
|
||||
// Lazy ListFormat data has the following structure:
|
||||
//
|
||||
// {
|
||||
// requestedLocales: List of locales,
|
||||
// type: "conjunction" / "disjunction" / "unit",
|
||||
// style: "long" / "short" / "narrow",
|
||||
//
|
||||
// opt: // opt object computed in InitializeListFormat
|
||||
// {
|
||||
// localeMatcher: "lookup" / "best fit",
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Note that lazy data is only installed as a final step of initialization,
|
||||
// so every ListFormat lazy data object has *all* these properties, never a
|
||||
// subset of them.
|
||||
var lazyListFormatData = std_Object_create(null);
|
||||
|
||||
// Step 3.
|
||||
var requestedLocales = CanonicalizeLocaleList(locales);
|
||||
lazyListFormatData.requestedLocales = requestedLocales;
|
||||
|
||||
// Steps 4-5.
|
||||
if (options === undefined)
|
||||
options = std_Object_create(null);
|
||||
else
|
||||
options = ToObject(options);
|
||||
|
||||
// Step 6.
|
||||
var opt = new Record();
|
||||
lazyListFormatData.opt = opt;
|
||||
|
||||
// Steps 7-8.
|
||||
let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
|
||||
opt.localeMatcher = matcher;
|
||||
|
||||
// Compute formatting options.
|
||||
|
||||
// We're currently only supporting "conjunctive-long" list formatters due to
|
||||
// missing ICU APIs: https://unicode-org.atlassian.net/browse/ICU-12863
|
||||
|
||||
// Steps 12-13.
|
||||
var type = GetOption(options, "type", "string", ["conjunction"], "conjunction");
|
||||
lazyListFormatData.type = type;
|
||||
|
||||
// Steps 14-15.
|
||||
var style = GetOption(options, "style", "string", ["long"], "long");
|
||||
lazyListFormatData.style = style;
|
||||
|
||||
// We've done everything that must be done now: mark the lazy data as fully
|
||||
// computed and install it.
|
||||
initializeIntlObject(listFormat, "ListFormat", lazyListFormatData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subset of the given locale list for which this locale list has a
|
||||
* matching (possibly fallback) locale. Locales appear in the same order in the
|
||||
* returned list as in the input list.
|
||||
*/
|
||||
function Intl_ListFormat_supportedLocalesOf(locales /*, options*/) {
|
||||
var options = arguments.length > 1 ? arguments[1] : undefined;
|
||||
|
||||
// Step 1.
|
||||
var availableLocales = "ListFormat";
|
||||
|
||||
// Step 2.
|
||||
var requestedLocales = CanonicalizeLocaleList(locales);
|
||||
|
||||
// Step 3.
|
||||
return SupportedLocales(availableLocales, requestedLocales, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* StringListFromIterable ( iterable )
|
||||
*/
|
||||
function StringListFromIterable(iterable, methodName) {
|
||||
// Step 1.
|
||||
if (iterable === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
var list = [];
|
||||
|
||||
// Steps 2, 4-5.
|
||||
for (var element of allowContentIter(iterable)) {
|
||||
// Step 5.b.ii.
|
||||
if (typeof element !== "string") {
|
||||
ThrowTypeError(JSMSG_NOT_EXPECTED_TYPE, methodName, "string", typeof element);
|
||||
}
|
||||
|
||||
// Step 5.b.iii.
|
||||
_DefineDataProperty(list, list.length, element);
|
||||
}
|
||||
|
||||
// Step 6.
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intl.ListFormat.prototype.format ( list )
|
||||
*/
|
||||
function Intl_ListFormat_format(list) {
|
||||
// Step 1.
|
||||
var listFormat = this;
|
||||
|
||||
// Steps 2-3.
|
||||
if (!IsObject(listFormat) || (listFormat = GuardToListFormat(listFormat)) === null) {
|
||||
return callFunction(CallListFormatMethodIfWrapped, this, list,
|
||||
"Intl_ListFormat_format");
|
||||
}
|
||||
|
||||
// Ensure the ListFormat internals are resolved.
|
||||
getListFormatInternals(listFormat);
|
||||
|
||||
// Step 4.
|
||||
var stringList = StringListFromIterable(list, "format");
|
||||
|
||||
// Step 5.
|
||||
return intl_FormatList(listFormat, stringList, /* formatToParts = */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intl.ListFormat.prototype.formatToParts ( list )
|
||||
*/
|
||||
function Intl_ListFormat_formatToParts(list) {
|
||||
// Step 1.
|
||||
var listFormat = this;
|
||||
|
||||
// Steps 2-3.
|
||||
if (!IsObject(listFormat) || (listFormat = GuardToListFormat(listFormat)) === null) {
|
||||
return callFunction(CallListFormatMethodIfWrapped, this, list,
|
||||
"Intl_ListFormat_formatToParts");
|
||||
}
|
||||
|
||||
// Ensure the ListFormat internals are resolved.
|
||||
getListFormatInternals(listFormat);
|
||||
|
||||
// Step 4.
|
||||
var stringList = StringListFromIterable(list, "formatToParts");
|
||||
|
||||
// Step 5.
|
||||
return intl_FormatList(listFormat, stringList, /* formatToParts = */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resolved options for a ListFormat object.
|
||||
*/
|
||||
function Intl_ListFormat_resolvedOptions() {
|
||||
// Step 1.
|
||||
var listFormat = this;
|
||||
|
||||
// Steps 2-3.
|
||||
if (!IsObject(listFormat) || (listFormat = GuardToListFormat(listFormat)) === null) {
|
||||
return callFunction(CallListFormatMethodIfWrapped, this,
|
||||
"Intl_ListFormat_resolvedOptions");
|
||||
}
|
||||
|
||||
var internals = getListFormatInternals(listFormat);
|
||||
|
||||
// Steps 4-5.
|
||||
var result = {
|
||||
locale: internals.locale,
|
||||
type: internals.type,
|
||||
style: internals.style,
|
||||
};
|
||||
|
||||
// Step 6.
|
||||
return result;
|
||||
}
|
|
@ -474,6 +474,7 @@ bool js::intl::SharedIntlData::isSupportedLocale(JSContext* cx,
|
|||
*supported = collatorSupportedLocales.has(lookup);
|
||||
return true;
|
||||
case SupportedLocaleKind::DateTimeFormat:
|
||||
case SupportedLocaleKind::ListFormat:
|
||||
case SupportedLocaleKind::NumberFormat:
|
||||
case SupportedLocaleKind::PluralRules:
|
||||
case SupportedLocaleKind::RelativeTimeFormat:
|
||||
|
|
|
@ -195,7 +195,7 @@ class SharedIntlData {
|
|||
// unum_[count,get]Available() return the same results as their
|
||||
// uloc_[count,get]Available() counterparts.
|
||||
//
|
||||
// UPluralRules and URelativeDateTimeFormatter:
|
||||
// UListFormatter, UPluralRules, and URelativeDateTimeFormatter:
|
||||
// We're going to use ULocale availableLocales as per ICU recommendation:
|
||||
// https://unicode-org.atlassian.net/browse/ICU-12756
|
||||
LocaleSet supportedLocales;
|
||||
|
@ -224,6 +224,7 @@ class SharedIntlData {
|
|||
enum class SupportedLocaleKind {
|
||||
Collator,
|
||||
DateTimeFormat,
|
||||
ListFormat,
|
||||
NumberFormat,
|
||||
PluralRules,
|
||||
RelativeTimeFormat
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
\
|
||||
_(IntlGuardToCollator) \
|
||||
_(IntlGuardToDateTimeFormat) \
|
||||
_(IntlGuardToListFormat) \
|
||||
_(IntlGuardToNumberFormat) \
|
||||
_(IntlGuardToPluralRules) \
|
||||
_(IntlGuardToRelativeTimeFormat) \
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#ifdef ENABLE_INTL_API
|
||||
# include "builtin/intl/Collator.h"
|
||||
# include "builtin/intl/DateTimeFormat.h"
|
||||
# include "builtin/intl/ListFormat.h"
|
||||
# include "builtin/intl/NumberFormat.h"
|
||||
# include "builtin/intl/PluralRules.h"
|
||||
# include "builtin/intl/RelativeTimeFormat.h"
|
||||
|
@ -110,6 +111,7 @@ static bool CanInlineCrossRealm(InlinableNative native) {
|
|||
|
||||
case InlinableNative::IntlGuardToCollator:
|
||||
case InlinableNative::IntlGuardToDateTimeFormat:
|
||||
case InlinableNative::IntlGuardToListFormat:
|
||||
case InlinableNative::IntlGuardToNumberFormat:
|
||||
case InlinableNative::IntlGuardToPluralRules:
|
||||
case InlinableNative::IntlGuardToRelativeTimeFormat:
|
||||
|
@ -317,6 +319,8 @@ IonBuilder::InliningResult IonBuilder::inlineNativeCall(CallInfo& callInfo,
|
|||
return inlineGuardToClass(callInfo, &CollatorObject::class_);
|
||||
case InlinableNative::IntlGuardToDateTimeFormat:
|
||||
return inlineGuardToClass(callInfo, &DateTimeFormatObject::class_);
|
||||
case InlinableNative::IntlGuardToListFormat:
|
||||
return inlineGuardToClass(callInfo, &ListFormatObject::class_);
|
||||
case InlinableNative::IntlGuardToNumberFormat:
|
||||
return inlineGuardToClass(callInfo, &NumberFormatObject::class_);
|
||||
case InlinableNative::IntlGuardToPluralRules:
|
||||
|
@ -326,6 +330,7 @@ IonBuilder::InliningResult IonBuilder::inlineNativeCall(CallInfo& callInfo,
|
|||
#else
|
||||
case InlinableNative::IntlGuardToCollator:
|
||||
case InlinableNative::IntlGuardToDateTimeFormat:
|
||||
case InlinableNative::IntlGuardToListFormat:
|
||||
case InlinableNative::IntlGuardToNumberFormat:
|
||||
case InlinableNative::IntlGuardToPluralRules:
|
||||
case InlinableNative::IntlGuardToRelativeTimeFormat:
|
||||
|
|
|
@ -2608,6 +2608,10 @@ extern bool AddMozDateTimeFormatConstructor(JSContext* cx,
|
|||
|
||||
// Create and add the Intl.Locale constructor function to the provided object.
|
||||
extern bool AddLocaleConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
|
||||
|
||||
// Create and add the Intl.ListFormat constructor function to the provided
|
||||
// object.
|
||||
extern bool AddListFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
|
||||
#endif // ENABLE_INTL_API
|
||||
|
||||
class MOZ_STACK_CLASS JS_FRIEND_API AutoAssertNoContentJS {
|
||||
|
|
|
@ -383,6 +383,7 @@ if CONFIG['ENABLE_INTL_API']:
|
|||
'builtin/intl/IntlObject.cpp',
|
||||
'builtin/intl/LanguageTag.cpp',
|
||||
'builtin/intl/LanguageTagGenerated.cpp',
|
||||
'builtin/intl/ListFormat.cpp',
|
||||
'builtin/intl/Locale.cpp',
|
||||
'builtin/intl/NumberFormat.cpp',
|
||||
'builtin/intl/PluralRules.cpp',
|
||||
|
@ -481,6 +482,7 @@ selfhosted_inputs = [
|
|||
'builtin/intl/CurrencyDataGenerated.js',
|
||||
'builtin/intl/DateTimeFormat.js',
|
||||
'builtin/intl/IntlObject.js',
|
||||
'builtin/intl/ListFormat.js',
|
||||
'builtin/intl/NumberFormat.js',
|
||||
'builtin/intl/PluralRules.js',
|
||||
'builtin/intl/RelativeTimeFormat.js',
|
||||
|
|
|
@ -1276,6 +1276,10 @@ static bool AddIntlExtras(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!js::AddListFormatConstructor(cx, intl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
// |reftest| skip-if(!this.hasOwnProperty('Intl')||(!this.Intl.ListFormat&&!this.hasOwnProperty('addIntlExtras')))
|
||||
|
||||
// Note: This is a stripped down version of conjunction-type.js. When we support
|
||||
// "short" and "narrow" styles, we can remove this file.
|
||||
|
||||
const {Element, Literal} = ListFormatParts;
|
||||
|
||||
// Test with zero elements.
|
||||
{
|
||||
const list = [];
|
||||
const expected = [];
|
||||
const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
|
||||
|
||||
for (let locale of locales) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "conjunction", style: "long"});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Test with one element.
|
||||
{
|
||||
const list = ["A"];
|
||||
const expected = [Element(list[0])];
|
||||
const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
|
||||
|
||||
for (let locale of locales) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "conjunction", style: "long"});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Test with two elements to cover the [[Template2]] case.
|
||||
{
|
||||
const list = ["A", "B"];
|
||||
|
||||
const testData = {
|
||||
"ar": [Element("A"), Literal(" و"), Element("B")],
|
||||
"de": [Element("A"), Literal(" und "), Element("B")],
|
||||
"en": [Element("A"), Literal(" and "), Element("B")],
|
||||
"es": [Element("A"), Literal(" y "), Element("B")],
|
||||
"ja": [Element("A"), Literal("、"), Element("B")],
|
||||
"nl": [Element("A"), Literal(" en "), Element("B")],
|
||||
"th": [Element("A"), Literal("และ"), Element("B")],
|
||||
"zh": [Element("A"), Literal("和"), Element("B")],
|
||||
};
|
||||
|
||||
for (let [locale, expected] of Object.entries(testData)) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "conjunction", style: "long"});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Test with more than two elements.
|
||||
//
|
||||
// Use four elements to cover all template parts ([[TemplateStart]], [[TemplateMiddle]], and
|
||||
// [[TemplateEnd]]).
|
||||
{
|
||||
const list = ["A", "B", "C", "D"];
|
||||
|
||||
const testData = {
|
||||
"ar": [Element("A"), Literal("، "), Element("B"), Literal("، "), Element("C"), Literal("، و"), Element("D")],
|
||||
"de": [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" und "), Element("D")],
|
||||
"en": [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", and "), Element("D")],
|
||||
"es": [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" y "), Element("D")],
|
||||
"ja": [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("、"), Element("D")],
|
||||
"nl": [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" en "), Element("D")],
|
||||
"th": [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" และ"), Element("D")],
|
||||
"zh": [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("和"), Element("D")],
|
||||
};
|
||||
|
||||
for (let [locale, expected] of Object.entries(testData)) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "conjunction", style: "long"});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -0,0 +1,112 @@
|
|||
// |reftest| skip -- "short" and "narrow" style are currently unsupported
|
||||
|
||||
// Note: Use the same test locales as used in unit-type.js
|
||||
|
||||
const {Element, Literal} = ListFormatParts;
|
||||
const styles = ["long", "short", "narrow"];
|
||||
|
||||
// Test with zero elements.
|
||||
{
|
||||
const list = [];
|
||||
const expected = [];
|
||||
const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
|
||||
|
||||
for (let locale of locales) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "conjunction", style});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test with one element.
|
||||
{
|
||||
const list = ["A"];
|
||||
const expected = [Element(list[0])];
|
||||
const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
|
||||
|
||||
for (let locale of locales) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "conjunction", style});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test with two elements to cover the [[Template2]] case.
|
||||
{
|
||||
const list = ["A", "B"];
|
||||
|
||||
const testData = {
|
||||
"ar": { long: [Element("A"), Literal(" و"), Element("B")] },
|
||||
"de": { long: [Element("A"), Literal(" und "), Element("B")] },
|
||||
"en": {
|
||||
long: [Element("A"), Literal(" and "), Element("B")],
|
||||
short: [Element("A"), Literal(" & "), Element("B")],
|
||||
narrow: [Element("A"), Literal(", "), Element("B")],
|
||||
},
|
||||
"es": { long: [Element("A"), Literal(" y "), Element("B")] },
|
||||
"ja": { long: [Element("A"), Literal("、"), Element("B")] },
|
||||
"nl": { long: [Element("A"), Literal(" en "), Element("B")] },
|
||||
"th": { long: [Element("A"), Literal("และ"), Element("B")] },
|
||||
"zh": { long: [Element("A"), Literal("和"), Element("B")] },
|
||||
};
|
||||
|
||||
for (let [locale, localeData] of Object.entries(testData)) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "conjunction", style});
|
||||
let {[style]: expected = localeData.long} = localeData;
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test with more than two elements.
|
||||
//
|
||||
// Use four elements to cover all template parts ([[TemplateStart]], [[TemplateMiddle]], and
|
||||
// [[TemplateEnd]]).
|
||||
{
|
||||
const list = ["A", "B", "C", "D"];
|
||||
|
||||
const testData = {
|
||||
"ar": {
|
||||
long: [Element("A"), Literal("، "), Element("B"), Literal("، "), Element("C"), Literal("، و"), Element("D")],
|
||||
short: [Element("A"), Literal("، و"), Element("B"), Literal("، و"), Element("C"), Literal("، و"), Element("D")],
|
||||
narrow: [Element("A"), Literal(" و"), Element("B"), Literal(" و"), Element("C"), Literal(" و"), Element("D")],
|
||||
},
|
||||
"de": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" und "), Element("D")],
|
||||
},
|
||||
"en": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", and "), Element("D")],
|
||||
short: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", & "), Element("D")],
|
||||
narrow: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
|
||||
},
|
||||
"es": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" y "), Element("D")],
|
||||
},
|
||||
"ja": {
|
||||
long: [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("、"), Element("D")],
|
||||
},
|
||||
"nl": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" en "), Element("D")],
|
||||
},
|
||||
"th": {
|
||||
long: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" และ"), Element("D")],
|
||||
},
|
||||
"zh": {
|
||||
long: [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("和"), Element("D")],
|
||||
},
|
||||
};
|
||||
|
||||
for (let [locale, localeData] of Object.entries(testData)) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "conjunction", style});
|
||||
let {[style]: expected = localeData.long} = localeData;
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -0,0 +1,43 @@
|
|||
// |reftest| skip-if(!this.hasOwnProperty('Intl')||(!this.Intl.Locale&&!this.hasOwnProperty('addIntlExtras')))
|
||||
|
||||
var g = newGlobal();
|
||||
addIntlListFormat(g);
|
||||
|
||||
var locale = "en";
|
||||
var list = ["a", "b", "c"];
|
||||
|
||||
var listFormat = new Intl.ListFormat(locale);
|
||||
var ccwListFormat = new g.Intl.ListFormat(locale);
|
||||
|
||||
// Intl.ListFormat.prototype.format
|
||||
{
|
||||
var fn = Intl.ListFormat.prototype.format;
|
||||
|
||||
var expectedValue = fn.call(listFormat, list);
|
||||
var actualValue = fn.call(ccwListFormat, list);
|
||||
|
||||
assertEq(actualValue, expectedValue);
|
||||
}
|
||||
|
||||
// Intl.ListFormat.prototype.formatToParts
|
||||
{
|
||||
var fn = Intl.ListFormat.prototype.formatToParts;
|
||||
|
||||
var expectedValue = fn.call(listFormat, list);
|
||||
var actualValue = fn.call(ccwListFormat, list);
|
||||
|
||||
assertDeepEq(actualValue, expectedValue);
|
||||
}
|
||||
|
||||
// Intl.ListFormat.prototype.resolvedOptions
|
||||
{
|
||||
var fn = Intl.ListFormat.prototype.resolvedOptions;
|
||||
|
||||
var expectedValue = fn.call(listFormat);
|
||||
var actualValue = fn.call(ccwListFormat);
|
||||
|
||||
assertDeepEq(actualValue, expectedValue);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -0,0 +1,104 @@
|
|||
// |reftest| skip -- "disjunction" type currently not supported
|
||||
|
||||
// Note: Use the same test locales as used in unit-type.js
|
||||
|
||||
const {Element, Literal} = ListFormatParts;
|
||||
const styles = ["long", "short", "narrow"];
|
||||
|
||||
// Test with zero elements.
|
||||
{
|
||||
const list = [];
|
||||
const expected = [];
|
||||
const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
|
||||
|
||||
for (let locale of locales) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "disjunction", style});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test with one element.
|
||||
{
|
||||
const list = ["A"];
|
||||
const expected = [Element(list[0])];
|
||||
const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
|
||||
|
||||
for (let locale of locales) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "disjunction", style});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test with two elements to cover the [[Template2]] case.
|
||||
{
|
||||
const list = ["A", "B"];
|
||||
|
||||
const testData = {
|
||||
"ar": { long: [Element("A"), Literal(" أو "), Element("B")] },
|
||||
"de": { long: [Element("A"), Literal(" oder "), Element("B")] },
|
||||
"en": { long: [Element("A"), Literal(" or "), Element("B")] },
|
||||
"es": { long: [Element("A"), Literal(" o "), Element("B")] },
|
||||
"ja": { long: [Element("A"), Literal("または"), Element("B")] },
|
||||
"nl": { long: [Element("A"), Literal(" of "), Element("B")] },
|
||||
"th": { long: [Element("A"), Literal(" หรือ "), Element("B")] },
|
||||
"zh": { long: [Element("A"), Literal("或"), Element("B")] },
|
||||
};
|
||||
|
||||
for (let [locale, localeData] of Object.entries(testData)) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "disjunction", style});
|
||||
let {[style]: expected = localeData.long} = localeData;
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test with more than two elements.
|
||||
//
|
||||
// Use four elements to cover all template parts ([[TemplateStart]], [[TemplateMiddle]], and
|
||||
// [[TemplateEnd]]).
|
||||
{
|
||||
const list = ["A", "B", "C", "D"];
|
||||
|
||||
const testData = {
|
||||
"ar": {
|
||||
long: [Element("A"), Literal(" و"), Element("B"), Literal(" و"), Element("C"), Literal(" أو "), Element("D")],
|
||||
},
|
||||
"de": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" oder "), Element("D")],
|
||||
},
|
||||
"en": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", or "), Element("D")],
|
||||
},
|
||||
"es": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" o "), Element("D")],
|
||||
},
|
||||
"ja": {
|
||||
long: [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("、または"), Element("D")],
|
||||
},
|
||||
"nl": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" of "), Element("D")],
|
||||
},
|
||||
"th": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" หรือ "), Element("D")],
|
||||
},
|
||||
"zh": {
|
||||
long: [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("或"), Element("D")],
|
||||
},
|
||||
};
|
||||
|
||||
for (let [locale, localeData] of Object.entries(testData)) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "disjunction", style});
|
||||
let {[style]: expected = localeData.long} = localeData;
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -0,0 +1,40 @@
|
|||
// |reftest| skip-if(!this.hasOwnProperty('Intl')||(!this.Intl.Locale&&!this.hasOwnProperty('addIntlExtras')))
|
||||
|
||||
var locale = "en";
|
||||
var list = ["a", "b", "c"];
|
||||
|
||||
var listFormat = new Intl.ListFormat(locale);
|
||||
var scwListFormat = wrapWithProto(listFormat, Intl.ListFormat.prototype);
|
||||
|
||||
// Intl.ListFormat.prototype.format
|
||||
{
|
||||
var fn = Intl.ListFormat.prototype.format;
|
||||
|
||||
var expectedValue = fn.call(listFormat, list);
|
||||
var actualValue = fn.call(scwListFormat, list);
|
||||
|
||||
assertEq(actualValue, expectedValue);
|
||||
}
|
||||
|
||||
// Intl.ListFormat.prototype.formatToParts
|
||||
{
|
||||
var fn = Intl.ListFormat.prototype.formatToParts;
|
||||
|
||||
var expectedValue = fn.call(listFormat, list);
|
||||
var actualValue = fn.call(scwListFormat, list);
|
||||
|
||||
assertDeepEq(actualValue, expectedValue);
|
||||
}
|
||||
|
||||
// Intl.ListFormat.prototype.resolvedOptions
|
||||
{
|
||||
var fn = Intl.ListFormat.prototype.resolvedOptions;
|
||||
|
||||
var expectedValue = fn.call(listFormat);
|
||||
var actualValue = fn.call(scwListFormat);
|
||||
|
||||
assertDeepEq(actualValue, expectedValue);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -0,0 +1,36 @@
|
|||
// Add |Intl.ListFormat| to the Intl object if not already present.
|
||||
function addIntlListFormat(global) {
|
||||
if (!global.Intl.ListFormat && typeof global.addIntlExtras === "function") {
|
||||
let obj = {};
|
||||
global.addIntlExtras(obj);
|
||||
|
||||
Object.defineProperty(global.Intl, "ListFormat", {
|
||||
value: obj.ListFormat,
|
||||
writable: true, enumerable: false, configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addIntlListFormat(this);
|
||||
|
||||
function GenericPartCreator(type) {
|
||||
return str => ({ type, value: str });
|
||||
}
|
||||
|
||||
const ListFormatParts = {
|
||||
Element: GenericPartCreator("element"),
|
||||
Literal: GenericPartCreator("literal"),
|
||||
};
|
||||
|
||||
function assertParts(lf, x, expected) {
|
||||
var parts = lf.formatToParts(x);
|
||||
assertEq(parts.map(part => part.value).join(""), lf.format(x),
|
||||
"formatToParts and format must agree");
|
||||
|
||||
var len = parts.length;
|
||||
assertEq(len, expected.length, "parts count mismatch");
|
||||
for (var i = 0; i < len; i++) {
|
||||
assertEq(parts[i].type, expected[i].type, "type mismatch at " + i);
|
||||
assertEq(parts[i].value, expected[i].value, "value mismatch at " + i);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// |reftest| skip-if(!this.hasOwnProperty('Intl')||(!this.Intl.ListFormat&&!this.hasOwnProperty('addIntlExtras')))
|
||||
|
||||
// Intl.ListFormat.supportedLocalesOf returns an empty array for unsupported locales.
|
||||
assertEq(Intl.ListFormat.supportedLocalesOf("art-lobjan").length, 0);
|
||||
|
||||
// And a non-empty array for supported locales.
|
||||
assertEq(Intl.ListFormat.supportedLocalesOf("en").length, 1);
|
||||
assertEq(Intl.ListFormat.supportedLocalesOf("en")[0], "en");
|
||||
|
||||
// If the locale is supported per |Intl.ListFormat.supportedLocalesOf|, the resolved locale
|
||||
// should reflect this.
|
||||
for (let locale of Intl.ListFormat.supportedLocalesOf(["en", "de", "th", "ar"])) {
|
||||
let lf = new Intl.ListFormat(locale);
|
||||
assertEq(lf.resolvedOptions().locale, locale);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -0,0 +1,149 @@
|
|||
// |reftest| skip -- "unit" type currently not supported
|
||||
|
||||
const {Element, Literal} = ListFormatParts;
|
||||
const styles = ["long", "short", "narrow"];
|
||||
|
||||
// Test with zero elements.
|
||||
{
|
||||
const list = [];
|
||||
const expected = [];
|
||||
const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
|
||||
|
||||
for (let locale of locales) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "unit", style});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test with one element.
|
||||
{
|
||||
const list = ["A"];
|
||||
const expected = [Element(list[0])];
|
||||
const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
|
||||
|
||||
for (let locale of locales) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "unit", style});
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test with two elements to cover the [[Template2]] case.
|
||||
{
|
||||
const list = ["A", "B"];
|
||||
|
||||
const testData = {
|
||||
"ar": {
|
||||
long: [Element("A"), Literal(" و"), Element("B")],
|
||||
narrow: [Element("A"), Literal("، "), Element("B")],
|
||||
},
|
||||
"de": {
|
||||
long: [Element("A"), Literal(", "), Element("B")],
|
||||
},
|
||||
"en": {
|
||||
long: [Element("A"), Literal(", "), Element("B")],
|
||||
narrow: [Element("A"), Literal(" "), Element("B")],
|
||||
},
|
||||
"es": {
|
||||
long: [Element("A"), Literal(" y "), Element("B")],
|
||||
narrow: [Element("A"), Literal(" "), Element("B")],
|
||||
},
|
||||
"ja": {
|
||||
long: [Element("A"), Literal(" "), Element("B")],
|
||||
narrow: [Element("A"), Element("B")],
|
||||
},
|
||||
"nl": {
|
||||
long: [Element("A"), Literal(" en "), Element("B")],
|
||||
short: [Element("A"), Literal(", "), Element("B")],
|
||||
narrow: [Element("A"), Literal(", "), Element("B")],
|
||||
},
|
||||
"th": {
|
||||
long: [Element("A"), Literal(" และ "), Element("B")],
|
||||
short: [Element("A"), Literal(" "), Element("B")],
|
||||
narrow: [Element("A"), Literal(" "), Element("B")],
|
||||
},
|
||||
"zh": {
|
||||
long: [Element("A"), Element("B")],
|
||||
},
|
||||
};
|
||||
|
||||
for (let [locale, localeData] of Object.entries(testData)) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "unit", style});
|
||||
let {[style]: expected = localeData.long} = localeData;
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test with more than two elements.
|
||||
//
|
||||
// Use four elements to cover all template parts ([[TemplateStart]], [[TemplateMiddle]], and
|
||||
// [[TemplateEnd]]).
|
||||
{
|
||||
const list = ["A", "B", "C", "D"];
|
||||
|
||||
const testData = {
|
||||
// non-ASCII case
|
||||
"ar": {
|
||||
long: [Element("A"), Literal("، و"), Element("B"), Literal("، و"), Element("C"), Literal("، و"), Element("D")],
|
||||
narrow: [Element("A"), Literal("، "), Element("B"), Literal("، "), Element("C"), Literal("، "), Element("D")],
|
||||
},
|
||||
|
||||
// all values are equal
|
||||
"de": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" und "), Element("D")],
|
||||
},
|
||||
|
||||
// long and short values are equal
|
||||
"en": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
|
||||
narrow: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" "), Element("D")],
|
||||
},
|
||||
|
||||
// all values are different
|
||||
"es": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" y "), Element("D")],
|
||||
short: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
|
||||
narrow: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" "), Element("D")],
|
||||
},
|
||||
|
||||
// no spacing for narrow case
|
||||
"ja": {
|
||||
long: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" "), Element("D")],
|
||||
narrow: [Element("A"), Element("B"), Element("C"), Element("D")],
|
||||
},
|
||||
|
||||
// short and narrow values are equal
|
||||
"nl": {
|
||||
long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" en "), Element("D")],
|
||||
short: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
|
||||
narrow: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
|
||||
},
|
||||
|
||||
// another non-ASCII case
|
||||
"th": {
|
||||
long: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" และ "), Element("D")],
|
||||
narrow: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" "), Element("D")],
|
||||
},
|
||||
|
||||
// no whitespace at all
|
||||
"zh": {
|
||||
long: [Element("A"), Element("B"), Element("C"), Element("D")],
|
||||
},
|
||||
};
|
||||
|
||||
for (let [locale, localeData] of Object.entries(testData)) {
|
||||
for (let style of styles) {
|
||||
let lf = new Intl.ListFormat(locale, {type: "unit", style});
|
||||
let {[style]: expected = localeData.long} = localeData;
|
||||
assertParts(lf, list, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -126,6 +126,7 @@
|
|||
MACRO(dotInitializers, dotInitializers, ".initializers") \
|
||||
MACRO(dotFieldKeys, dotFieldKeys, ".fieldKeys") \
|
||||
MACRO(each, each, "each") \
|
||||
MACRO(element, element, "element") \
|
||||
MACRO(elementType, elementType, "elementType") \
|
||||
MACRO(else, else_, "else") \
|
||||
MACRO(empty, empty, "") \
|
||||
|
@ -220,6 +221,7 @@
|
|||
MACRO(InitializeCollator, InitializeCollator, "InitializeCollator") \
|
||||
MACRO(InitializeDateTimeFormat, InitializeDateTimeFormat, \
|
||||
"InitializeDateTimeFormat") \
|
||||
MACRO(InitializeListFormat, InitializeListFormat, "InitializeListFormat") \
|
||||
MACRO(InitializeLocale, InitializeLocale, "InitializeLocale") \
|
||||
MACRO(InitializeNumberFormat, InitializeNumberFormat, \
|
||||
"InitializeNumberFormat") \
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#ifdef ENABLE_INTL_API
|
||||
# include "builtin/intl/Collator.h"
|
||||
# include "builtin/intl/DateTimeFormat.h"
|
||||
# include "builtin/intl/ListFormat.h"
|
||||
# include "builtin/intl/Locale.h"
|
||||
# include "builtin/intl/NumberFormat.h"
|
||||
# include "builtin/intl/PluralRules.h"
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
# include "builtin/intl/Collator.h"
|
||||
# include "builtin/intl/DateTimeFormat.h"
|
||||
# include "builtin/intl/IntlObject.h"
|
||||
# include "builtin/intl/ListFormat.h"
|
||||
# include "builtin/intl/Locale.h"
|
||||
# include "builtin/intl/NumberFormat.h"
|
||||
# include "builtin/intl/PluralRules.h"
|
||||
|
@ -2428,6 +2429,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 1, 0),
|
||||
JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2, 0),
|
||||
JS_FN("intl_FormatRelativeTime", intl_FormatRelativeTime, 4, 0),
|
||||
JS_FN("intl_FormatList", intl_FormatList, 3, 0),
|
||||
JS_FN("intl_toLocaleLowerCase", intl_toLocaleLowerCase, 2, 0),
|
||||
JS_FN("intl_toLocaleUpperCase", intl_toLocaleUpperCase, 2, 0),
|
||||
JS_FN("intl_ValidateAndCanonicalizeLanguageTag",
|
||||
|
@ -2440,6 +2442,9 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
JS_INLINABLE_FN("GuardToDateTimeFormat",
|
||||
intrinsic_GuardToBuiltin<DateTimeFormatObject>, 1, 0,
|
||||
IntlGuardToDateTimeFormat),
|
||||
JS_INLINABLE_FN("GuardToListFormat",
|
||||
intrinsic_GuardToBuiltin<ListFormatObject>, 1, 0,
|
||||
IntlGuardToListFormat),
|
||||
JS_INLINABLE_FN("GuardToNumberFormat",
|
||||
intrinsic_GuardToBuiltin<NumberFormatObject>, 1, 0,
|
||||
IntlGuardToNumberFormat),
|
||||
|
@ -2459,6 +2464,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
CallNonGenericSelfhostedMethod<Is<CollatorObject>>, 2, 0),
|
||||
JS_FN("CallDateTimeFormatMethodIfWrapped",
|
||||
CallNonGenericSelfhostedMethod<Is<DateTimeFormatObject>>, 2, 0),
|
||||
JS_FN("CallListFormatMethodIfWrapped",
|
||||
CallNonGenericSelfhostedMethod<Is<ListFormatObject>>, 2, 0),
|
||||
JS_FN("CallNumberFormatMethodIfWrapped",
|
||||
CallNonGenericSelfhostedMethod<Is<NumberFormatObject>>, 2, 0),
|
||||
JS_FN("CallPluralRulesMethodIfWrapped",
|
||||
|
|
Загрузка…
Ссылка в новой задаче