Coverage Report

Created: 2025-11-16 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <LibJS/Runtime/GlobalObject.h>
8
#include <LibJS/Runtime/Intl/AbstractOperations.h>
9
#include <LibJS/Runtime/Intl/DisplayNames.h>
10
11
namespace JS::Intl {
12
13
JS_DEFINE_ALLOCATOR(DisplayNames);
14
15
// 12 DisplayNames Objects, https://tc39.es/ecma402/#intl-displaynames-objects
16
DisplayNames::DisplayNames(Object& prototype)
17
0
    : Object(ConstructWithPrototypeTag::Tag, prototype)
18
0
{
19
0
}
20
21
void DisplayNames::set_type(StringView type)
22
0
{
23
0
    if (type == "language"sv)
24
0
        m_type = Type::Language;
25
0
    else if (type == "region"sv)
26
0
        m_type = Type::Region;
27
0
    else if (type == "script"sv)
28
0
        m_type = Type::Script;
29
0
    else if (type == "currency"sv)
30
0
        m_type = Type::Currency;
31
0
    else if (type == "calendar"sv)
32
0
        m_type = Type::Calendar;
33
0
    else if (type == "dateTimeField"sv)
34
0
        m_type = Type::DateTimeField;
35
0
    else
36
0
        VERIFY_NOT_REACHED();
37
0
}
38
39
StringView DisplayNames::type_string() const
40
0
{
41
0
    switch (m_type) {
42
0
    case Type::Language:
43
0
        return "language"sv;
44
0
    case Type::Region:
45
0
        return "region"sv;
46
0
    case Type::Script:
47
0
        return "script"sv;
48
0
    case Type::Currency:
49
0
        return "currency"sv;
50
0
    case Type::Calendar:
51
0
        return "calendar"sv;
52
0
    case Type::DateTimeField:
53
0
        return "dateTimeField"sv;
54
0
    default:
55
0
        VERIFY_NOT_REACHED();
56
0
    }
57
0
}
58
59
void DisplayNames::set_fallback(StringView fallback)
60
0
{
61
0
    if (fallback == "none"sv)
62
0
        m_fallback = Fallback::None;
63
0
    else if (fallback == "code"sv)
64
0
        m_fallback = Fallback::Code;
65
0
    else
66
0
        VERIFY_NOT_REACHED();
67
0
}
68
69
StringView DisplayNames::fallback_string() const
70
0
{
71
0
    switch (m_fallback) {
72
0
    case Fallback::None:
73
0
        return "none"sv;
74
0
    case Fallback::Code:
75
0
        return "code"sv;
76
0
    default:
77
0
        VERIFY_NOT_REACHED();
78
0
    }
79
0
}
80
81
void DisplayNames::set_language_display(StringView language_display)
82
0
{
83
0
    if (language_display == "dialect"sv)
84
0
        m_language_display = LanguageDisplay::Dialect;
85
0
    else if (language_display == "standard"sv)
86
0
        m_language_display = LanguageDisplay::Standard;
87
0
    else
88
0
        VERIFY_NOT_REACHED();
89
0
}
90
91
StringView DisplayNames::language_display_string() const
92
0
{
93
0
    VERIFY(m_language_display.has_value());
94
95
0
    switch (*m_language_display) {
96
0
    case LanguageDisplay::Dialect:
97
0
        return "dialect"sv;
98
0
    case LanguageDisplay::Standard:
99
0
        return "standard"sv;
100
0
    default:
101
0
        VERIFY_NOT_REACHED();
102
0
    }
103
0
}
104
105
// 12.5.1 CanonicalCodeForDisplayNames ( type, code ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames
106
ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::Type type, StringView code)
107
0
{
108
    // 1. If type is "language", then
109
0
    if (type == DisplayNames::Type::Language) {
110
        // a. If code does not match the unicode_language_id production, throw a RangeError exception.
111
0
        if (!::Locale::parse_unicode_language_id(code).has_value())
112
0
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "language"sv);
113
114
        // b. If IsStructurallyValidLanguageTag(code) is false, throw a RangeError exception.
115
0
        auto locale_id = is_structurally_valid_language_tag(code);
116
0
        if (!locale_id.has_value())
117
0
            return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, code);
118
119
        // c. Return ! CanonicalizeUnicodeLocaleId(code).
120
0
        auto canonicalized_tag = JS::Intl::canonicalize_unicode_locale_id(*locale_id);
121
0
        return PrimitiveString::create(vm, move(canonicalized_tag));
122
0
    }
123
124
    // 2. If type is "region", then
125
0
    if (type == DisplayNames::Type::Region) {
126
        // a. If code does not match the unicode_region_subtag production, throw a RangeError exception.
127
0
        if (!::Locale::is_unicode_region_subtag(code))
128
0
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "region"sv);
129
130
        // b. Return the ASCII-uppercase of code.
131
0
        return PrimitiveString::create(vm, code.to_uppercase_string());
132
0
    }
133
134
    // 3. If type is "script", then
135
0
    if (type == DisplayNames::Type::Script) {
136
        // a. If code does not match the unicode_script_subtag production, throw a RangeError exception.
137
0
        if (!::Locale::is_unicode_script_subtag(code))
138
0
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "script"sv);
139
140
        // Assert: The length of code is 4, and every code unit of code represents an ASCII letter (0x0041 through 0x005A and 0x0061 through 0x007A, both inclusive).
141
0
        VERIFY(code.length() == 4);
142
0
        VERIFY(all_of(code, is_ascii_alpha));
143
144
        // c. Let first be the ASCII-uppercase of the substring of code from 0 to 1.
145
        // d. Let rest be the ASCII-lowercase of the substring of code from 1.
146
        // e. Return the string-concatenation of first and rest.
147
0
        return PrimitiveString::create(vm, code.to_titlecase_string());
148
0
    }
149
150
    // 4. If type is "calendar", then
151
0
    if (type == DisplayNames::Type::Calendar) {
152
        // a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
153
0
        if (!::Locale::is_type_identifier(code))
154
0
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
155
156
        // b. If code uses any of the backwards compatibility syntax described in Unicode Technical Standard #35 LDML ยง 3.3 BCP 47 Conformance, throw a RangeError exception.
157
0
        if (code.contains('_'))
158
0
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
159
160
        // c. Return the ASCII-lowercase of code.
161
0
        return PrimitiveString::create(vm, code.to_lowercase_string());
162
0
    }
163
164
    // 5. If type is "dateTimeField", then
165
0
    if (type == DisplayNames::Type::DateTimeField) {
166
        // a. If the result of IsValidDateTimeFieldCode(code) is false, throw a RangeError exception.
167
0
        if (!is_valid_date_time_field_code(code))
168
0
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv);
169
170
        // b. Return code.
171
0
        return PrimitiveString::create(vm, code);
172
0
    }
173
174
    // 6. Assert: type is "currency".
175
0
    VERIFY(type == DisplayNames::Type::Currency);
176
177
    // 7. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
178
0
    if (!is_well_formed_currency_code(code))
179
0
        return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "currency"sv);
180
181
    // 8. Return the ASCII-uppercase of code.
182
0
    return PrimitiveString::create(vm, code.to_uppercase_string());
183
0
}
184
185
// 12.5.2 IsValidDateTimeFieldCode ( field ), https://tc39.es/ecma402/#sec-isvaliddatetimefieldcode
186
bool is_valid_date_time_field_code(StringView field)
187
0
{
188
    // 1. If field is listed in the Code column of Table 9, return true.
189
    // 2. Return false.
190
0
    return field.is_one_of("era"sv, "year"sv, "quarter"sv, "month"sv, "weekOfYear"sv, "weekday"sv, "day"sv, "dayPeriod"sv, "hour"sv, "minute"sv, "second"sv, "timeZoneName"sv);
191
0
}
192
193
}