diff options
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/StringPrototype.cpp | 1035 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/StringPrototype.h | 46 |
2 files changed, 543 insertions, 538 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp index 2142f0fe55..b8a1095ce2 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -59,6 +59,27 @@ static Optional<size_t> split_match(Utf16View const& haystack, size_t start, Utf return start + r; } +// 6.1.4.1 StringIndexOf ( string, searchValue, fromIndex ), https://tc39.es/ecma262/#sec-stringindexof +static Optional<size_t> string_index_of(Utf16View const& string, Utf16View const& search_value, size_t from_index) +{ + size_t string_length = string.length_in_code_units(); + size_t search_length = search_value.length_in_code_units(); + + if ((search_length == 0) && (from_index <= string_length)) + return from_index; + + if (search_length > string_length) + return {}; + + for (size_t i = from_index; i <= string_length - search_length; ++i) { + auto candidate = string.substring_view(i, search_length); + if (candidate == search_value) + return i; + } + + return {}; +} + // 11.1.4 CodePointAt ( string, position ), https://tc39.es/ecma262/#sec-codepointat CodePoint code_point_at(Utf16View const& string, size_t position) { @@ -82,27 +103,6 @@ CodePoint code_point_at(Utf16View const& string, size_t position) return { false, code_point, 2 }; } -// 6.1.4.1 StringIndexOf ( string, searchValue, fromIndex ), https://tc39.es/ecma262/#sec-stringindexof -static Optional<size_t> string_index_of(Utf16View const& string, Utf16View const& search_value, size_t from_index) -{ - size_t string_length = string.length_in_code_units(); - size_t search_length = search_value.length_in_code_units(); - - if ((search_length == 0) && (from_index <= string_length)) - return from_index; - - if (search_length > string_length) - return {}; - - for (size_t i = from_index; i <= string_length - search_length; ++i) { - auto candidate = string.substring_view(i, search_length); - if (candidate == search_value) - return i; - } - - return {}; -} - StringPrototype::StringPrototype(Realm& realm) : StringObject(*js_string(realm.vm(), String::empty()), *realm.intrinsics().object_prototype()) { @@ -114,40 +114,43 @@ void StringPrototype::initialize(Realm& realm) StringObject::initialize(realm); u8 attr = Attribute::Writable | Attribute::Configurable; + // 22.1.3 Properties of the String Prototype Object, https://tc39.es/ecma262/#sec-properties-of-the-string-prototype-object + define_native_function(realm, vm.names.at, at, 1, attr); define_native_function(realm, vm.names.charAt, char_at, 1, attr); define_native_function(realm, vm.names.charCodeAt, char_code_at, 1, attr); define_native_function(realm, vm.names.codePointAt, code_point_at, 1, attr); - define_native_function(realm, vm.names.repeat, repeat, 1, attr); - define_native_function(realm, vm.names.startsWith, starts_with, 1, attr); - define_native_function(realm, vm.names.endsWith, ends_with, 1, attr); - define_native_function(realm, vm.names.indexOf, index_of, 1, attr); - define_native_function(realm, vm.names.toLocaleLowerCase, to_locale_lowercase, 0, attr); - define_native_function(realm, vm.names.toLocaleUpperCase, to_locale_uppercase, 0, attr); - define_native_function(realm, vm.names.toLowerCase, to_lowercase, 0, attr); - define_native_function(realm, vm.names.toUpperCase, to_uppercase, 0, attr); - define_native_function(realm, vm.names.toString, to_string, 0, attr); - define_native_function(realm, vm.names.valueOf, value_of, 0, attr); - define_native_function(realm, vm.names.padStart, pad_start, 1, attr); - define_native_function(realm, vm.names.padEnd, pad_end, 1, attr); - define_native_function(realm, vm.names.trim, trim, 0, attr); - define_native_function(realm, vm.names.trimStart, trim_start, 0, attr); - define_direct_property(vm.names.trimLeft, get_without_side_effects(vm.names.trimStart), attr); - define_native_function(realm, vm.names.trimEnd, trim_end, 0, attr); - define_direct_property(vm.names.trimRight, get_without_side_effects(vm.names.trimEnd), attr); define_native_function(realm, vm.names.concat, concat, 1, attr); - define_native_function(realm, vm.names.substr, substr, 2, attr); - define_native_function(realm, vm.names.substring, substring, 2, attr); + define_native_function(realm, vm.names.endsWith, ends_with, 1, attr); define_native_function(realm, vm.names.includes, includes, 1, attr); - define_native_function(realm, vm.names.slice, slice, 2, attr); - define_native_function(realm, vm.names.split, split, 2, attr); + define_native_function(realm, vm.names.indexOf, index_of, 1, attr); define_native_function(realm, vm.names.lastIndexOf, last_index_of, 1, attr); - define_native_function(realm, vm.names.at, at, 1, attr); + define_native_function(realm, vm.names.localeCompare, locale_compare, 1, attr); define_native_function(realm, vm.names.match, match, 1, attr); define_native_function(realm, vm.names.matchAll, match_all, 1, attr); define_native_function(realm, vm.names.normalize, normalize, 0, attr); + define_native_function(realm, vm.names.padEnd, pad_end, 1, attr); + define_native_function(realm, vm.names.padStart, pad_start, 1, attr); + define_native_function(realm, vm.names.repeat, repeat, 1, attr); define_native_function(realm, vm.names.replace, replace, 2, attr); define_native_function(realm, vm.names.replaceAll, replace_all, 2, attr); define_native_function(realm, vm.names.search, search, 1, attr); + define_native_function(realm, vm.names.slice, slice, 2, attr); + define_native_function(realm, vm.names.split, split, 2, attr); + define_native_function(realm, vm.names.startsWith, starts_with, 1, attr); + define_native_function(realm, vm.names.substring, substring, 2, attr); + define_native_function(realm, vm.names.toLocaleLowerCase, to_locale_lowercase, 0, attr); + define_native_function(realm, vm.names.toLocaleUpperCase, to_locale_uppercase, 0, attr); + define_native_function(realm, vm.names.toLowerCase, to_lowercase, 0, attr); + define_native_function(realm, vm.names.toString, to_string, 0, attr); + define_native_function(realm, vm.names.toUpperCase, to_uppercase, 0, attr); + define_native_function(realm, vm.names.trim, trim, 0, attr); + define_native_function(realm, vm.names.trimEnd, trim_end, 0, attr); + define_native_function(realm, vm.names.trimStart, trim_start, 0, attr); + define_native_function(realm, vm.names.valueOf, value_of, 0, attr); + define_native_function(realm, *vm.well_known_symbol_iterator(), symbol_iterator, 0, attr); + + // B.2.2 Additional Properties of the String.prototype Object, https://tc39.es/ecma262/#sec-additional-properties-of-the-string.prototype-object + define_native_function(realm, vm.names.substr, substr, 2, attr); define_native_function(realm, vm.names.anchor, anchor, 1, attr); define_native_function(realm, vm.names.big, big, 0, attr); define_native_function(realm, vm.names.blink, blink, 0, attr); @@ -161,8 +164,8 @@ void StringPrototype::initialize(Realm& realm) define_native_function(realm, vm.names.strike, strike, 0, attr); define_native_function(realm, vm.names.sub, sub, 0, attr); define_native_function(realm, vm.names.sup, sup, 0, attr); - define_native_function(realm, vm.names.localeCompare, locale_compare, 1, attr); - define_native_function(realm, *vm.well_known_symbol_iterator(), symbol_iterator, 0, attr); + define_direct_property(vm.names.trimLeft, get_without_side_effects(vm.names.trimStart), attr); + define_direct_property(vm.names.trimRight, get_without_side_effects(vm.names.trimEnd), attr); } // thisStringValue ( value ), https://tc39.es/ecma262/#thisstringvalue @@ -175,6 +178,39 @@ static ThrowCompletionOr<PrimitiveString*> this_string_value(VM& vm, Value value return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "String"); } +// 22.1.3.1 String.prototype.at ( index ), https://tc39.es/ecma262/#sec-string.prototype.at +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::at) +{ + // 1. Let O be ? ToObject(this value). + auto string = TRY(utf16_string_from(vm)); + // 2. Let len be ? LengthOfArrayLike(O). + auto length = string.length_in_code_units(); + + // 3. Let relativeIndex be ? ToIntegerOrInfinity(index). + auto relative_index = TRY(vm.argument(0).to_integer_or_infinity(vm)); + if (Value(relative_index).is_infinity()) + return js_undefined(); + + Checked<size_t> index { 0 }; + // 4. If relativeIndex ≥ 0, then + if (relative_index >= 0) { + // a. Let k be relativeIndex. + index += relative_index; + } + // 5. Else, + else { + // a. Let k be len + relativeIndex. + index += length; + index -= -relative_index; + } + // 6. If k < 0 or k ≥ len, return undefined. + if (index.has_overflow() || index.value() >= length) + return js_undefined(); + + // 7. Return ? Get(O, ! ToString(𝔽(k))). + return js_string(vm, string.substring_view(index.value(), 1)); +} + // 22.1.3.2 String.prototype.charAt ( pos ), https://tc39.es/ecma262/#sec-string.prototype.charat JS_DEFINE_NATIVE_FUNCTION(StringPrototype::char_at) { @@ -209,34 +245,33 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::code_point_at) return Value(code_point.code_point); } -// 22.1.3.17 String.prototype.repeat ( count ), https://tc39.es/ecma262/#sec-string.prototype.repeat -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat) +// 22.1.3.5 String.prototype.concat ( ...args ), https://tc39.es/ecma262/#sec-string.prototype.concat +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::concat) { - auto string = TRY(ak_string_from(vm)); - - auto n = TRY(vm.argument(0).to_integer_or_infinity(vm)); + // 1. Let O be ? RequireObjectCoercible(this value). + auto object = TRY(require_object_coercible(vm, vm.this_value())); - if (n < 0) - return vm.throw_completion<RangeError>(ErrorType::StringRepeatCountMustBe, "positive"); + // 2. Let S be ? ToString(O). + auto* string = TRY(object.to_primitive_string(vm)); - if (Value(n).is_positive_infinity()) - return vm.throw_completion<RangeError>(ErrorType::StringRepeatCountMustBe, "finite"); + // 3. Let R be S. + auto* result = string; - if (n == 0) - return js_string(vm, String::empty()); + // 4. For each element next of args, do + for (size_t i = 0; i < vm.argument_count(); ++i) { + // a. Let nextString be ? ToString(next). + auto* next_string = TRY(vm.argument(i).to_primitive_string(vm)); - // NOTE: This is an optimization, it is not required by the specification but it produces equivalent behavior - if (string.is_empty()) - return js_string(vm, String::empty()); + // b. Set R to the string-concatenation of R and nextString. + result = js_rope_string(vm, *result, *next_string); + } - StringBuilder builder; - for (size_t i = 0; i < n; ++i) - builder.append(string); - return js_string(vm, builder.to_string()); + // 5. Return R. + return result; } -// 22.1.3.23 String.prototype.startsWith ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.startswith -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::starts_with) +// 22.1.3.7 String.prototype.endsWith ( searchString [ , endPosition ] ), https://tc39.es/ecma262/#sec-string.prototype.endswith +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::ends_with) { auto string = TRY(utf16_string_from(vm)); @@ -250,25 +285,25 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::starts_with) auto string_length = string.length_in_code_units(); auto search_length = search_string.length_in_code_units(); - size_t start = 0; + size_t end = string_length; if (!vm.argument(1).is_undefined()) { auto position = TRY(vm.argument(1).to_integer_or_infinity(vm)); - start = clamp(position, static_cast<double>(0), static_cast<double>(string_length)); + end = clamp(position, static_cast<double>(0), static_cast<double>(string_length)); } if (search_length == 0) return Value(true); - - size_t end = start + search_length; - if (end > string_length) + if (search_length > end) return Value(false); + size_t start = end - search_length; + auto substring_view = string.substring_view(start, end - start); return Value(substring_view == search_string.view()); } -// 22.1.3.7 String.prototype.endsWith ( searchString [ , endPosition ] ), https://tc39.es/ecma262/#sec-string.prototype.endswith -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::ends_with) +// 22.1.3.8 String.prototype.includes ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.includes +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::includes) { auto string = TRY(utf16_string_from(vm)); @@ -279,24 +314,15 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::ends_with) return vm.throw_completion<TypeError>(ErrorType::IsNotA, "searchString", "string, but a regular expression"); auto search_string = TRY(search_string_value.to_utf16_string(vm)); - auto string_length = string.length_in_code_units(); - auto search_length = search_string.length_in_code_units(); - size_t end = string_length; + size_t start = 0; if (!vm.argument(1).is_undefined()) { auto position = TRY(vm.argument(1).to_integer_or_infinity(vm)); - end = clamp(position, static_cast<double>(0), static_cast<double>(string_length)); + start = clamp(position, static_cast<double>(0), static_cast<double>(string.length_in_code_units())); } - if (search_length == 0) - return Value(true); - if (search_length > end) - return Value(false); - - size_t start = end - search_length; - - auto substring_view = string.substring_view(start, end - start); - return Value(substring_view == search_string.view()); + auto index = string_index_of(string.view(), search_string.view(), start); + return Value(index.has_value()); } // 22.1.3.9 String.prototype.indexOf ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.indexof @@ -318,119 +344,124 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::index_of) return index.has_value() ? Value(*index) : Value(-1); } -enum class TargetCase { - Lower, - Upper, -}; - -// 19.1.2.1 TransformCase ( S, locales, targetCase ), https://tc39.es/ecma402/#sec-transform-case -static ThrowCompletionOr<String> transform_case(VM& vm, StringView string, Value locales, TargetCase target_case) +// 22.1.3.10 String.prototype.lastIndexOf ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.lastindexof +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::last_index_of) { - // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). - auto requested_locales = TRY(Intl::canonicalize_locale_list(vm, locales)); - - Optional<Locale::LocaleID> requested_locale; - - // 2. If requestedLocales is not an empty List, then - if (!requested_locales.is_empty()) { - // a. Let requestedLocale be requestedLocales[0]. - requested_locale = Locale::parse_unicode_locale_id(requested_locales[0]); - } - // 3. Else, - else { - // a. Let requestedLocale be ! DefaultLocale(). - requested_locale = Locale::parse_unicode_locale_id(Locale::default_locale()); - } - VERIFY(requested_locale.has_value()); + auto string = TRY(utf16_string_from(vm)); - // 4. Let noExtensionsLocale be the String value that is requestedLocale with any Unicode locale extension sequences (6.2.1) removed. - requested_locale->remove_extension_type<Locale::LocaleExtension>(); - auto no_extensions_locale = requested_locale->to_string(); + auto search_string = TRY(vm.argument(0).to_utf16_string(vm)); + auto string_length = string.length_in_code_units(); + auto search_length = search_string.length_in_code_units(); - // 5. Let availableLocales be a List with language tags that includes the languages for which the Unicode Character Database contains language sensitive case mappings. Implementations may add additional language tags if they support case mapping for additional locales. - // 6. Let locale be ! BestAvailableLocale(availableLocales, noExtensionsLocale). - auto locale = Intl::best_available_locale(no_extensions_locale); + auto position = TRY(vm.argument(1).to_number(vm)); + double pos = position.is_nan() ? static_cast<double>(INFINITY) : TRY(position.to_integer_or_infinity(vm)); - // 7. If locale is undefined, set locale to "und". - if (!locale.has_value()) - locale = "und"sv; + size_t start = clamp(pos, static_cast<double>(0), static_cast<double>(string_length)); + Optional<size_t> last_index; - // 8. Let codePoints be StringToCodePoints(S). + for (size_t k = 0; (k <= start) && (k + search_length <= string_length); ++k) { + bool is_match = true; - String new_code_points; + for (size_t j = 0; j < search_length; ++j) { + if (string.code_unit_at(k + j) != search_string.code_unit_at(j)) { + is_match = false; + break; + } + } - switch (target_case) { - // 9. If targetCase is lower, then - case TargetCase::Lower: - // a. Let newCodePoints be a List whose elements are the result of a lowercase transformation of codePoints according to an implementation-derived algorithm using locale or the Unicode Default Case Conversion algorithm. - new_code_points = Unicode::to_unicode_lowercase_full(string, *locale); - break; - // 10. Else, - case TargetCase::Upper: - // a. Assert: targetCase is upper. - // b. Let newCodePoints be a List whose elements are the result of an uppercase transformation of codePoints according to an implementation-derived algorithm using locale or the Unicode Default Case Conversion algorithm. - new_code_points = Unicode::to_unicode_uppercase_full(string, *locale); - break; - default: - VERIFY_NOT_REACHED(); + if (is_match) + last_index = k; } - // 11. Return CodePointsToString(newCodePoints). - return new_code_points; + return last_index.has_value() ? Value(*last_index) : Value(-1); } -// 19.1.2 String.prototype.toLocaleLowerCase ( [ locales ] ), https://tc39.es/ecma402/#sup-string.prototype.tolocalelowercase -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_lowercase) +// 22.1.3.11 String.prototype.localeCompare ( that [ , reserved1 [ , reserved2 ] ] ), https://tc39.es/ecma262/#sec-string.prototype.localecompare +// 19.1.1 String.prototype.localeCompare ( that [ , locales [ , options ] ] ), https://tc39.es/ecma402/#sup-String.prototype.localeCompare +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::locale_compare) { - auto locales = vm.argument(0); + auto& realm = *vm.current_realm(); // 1. Let O be ? RequireObjectCoercible(this value). + auto object = TRY(require_object_coercible(vm, vm.this_value())); + // 2. Let S be ? ToString(O). - auto string = TRY(ak_string_from(vm)); + auto string = TRY(object.to_string(vm)); - // 3. Return ? TransformCase(S, locales, lower). - return js_string(vm, TRY(transform_case(vm, string, locales, TargetCase::Lower))); + // 3. Let thatValue be ? ToString(that). + auto that_value = TRY(vm.argument(0).to_string(vm)); + + // 4. Let collator be ? Construct(%Collator%, « locales, options »). + auto* collator = TRY(construct(vm, *realm.intrinsics().intl_collator_constructor(), vm.argument(1), vm.argument(2))); + + // 5. Return CompareStrings(collator, S, thatValue). + return Intl::compare_strings(static_cast<Intl::Collator&>(*collator), Utf8View(string), Utf8View(that_value)); } -// 19.1.3 String.prototype.toLocaleUpperCase ( [ locales ] ), https://tc39.es/ecma402/#sup-string.prototype.tolocaleuppercase -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_uppercase) +// 22.1.3.12 String.prototype.match ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.match +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match) { - auto locales = vm.argument(0); + auto this_object = TRY(require_object_coercible(vm, vm.this_value())); + auto regexp = vm.argument(0); + if (!regexp.is_nullish()) { + if (auto* matcher = TRY(regexp.get_method(vm, *vm.well_known_symbol_match()))) + return TRY(call(vm, *matcher, regexp, this_object)); + } - // 1. Let O be ? RequireObjectCoercible(this value). - // 2. Let S be ? ToString(O). - auto string = TRY(ak_string_from(vm)); + auto string = TRY(this_object.to_utf16_string(vm)); - // 3. Return ? TransformCase(S, locales, upper). - return js_string(vm, TRY(transform_case(vm, string, locales, TargetCase::Upper))); + auto rx = TRY(regexp_create(vm, regexp, js_undefined())); + return TRY(Value(rx).invoke(vm, *vm.well_known_symbol_match(), js_string(vm, move(string)))); } -// 22.1.3.27 String.prototype.toLowerCase ( ), https://tc39.es/ecma262/#sec-string.prototype.tolowercase -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_lowercase) +// 22.1.3.13 String.prototype.matchAll ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.matchall +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match_all) { - auto string = TRY(ak_string_from(vm)); - auto lowercase = Unicode::to_unicode_lowercase_full(string); - return js_string(vm, move(lowercase)); + auto this_object = TRY(require_object_coercible(vm, vm.this_value())); + auto regexp = vm.argument(0); + if (!regexp.is_nullish()) { + auto is_regexp = TRY(regexp.is_regexp(vm)); + if (is_regexp) { + auto flags = TRY(regexp.as_object().get("flags")); + auto flags_object = TRY(require_object_coercible(vm, flags)); + auto flags_string = TRY(flags_object.to_string(vm)); + if (!flags_string.contains('g')) + return vm.throw_completion<TypeError>(ErrorType::StringNonGlobalRegExp); + } + if (auto* matcher = TRY(regexp.get_method(vm, *vm.well_known_symbol_match_all()))) + return TRY(call(vm, *matcher, regexp, this_object)); + } + + auto string = TRY(this_object.to_utf16_string(vm)); + + auto rx = TRY(regexp_create(vm, regexp, js_string(vm, "g"))); + return TRY(Value(rx).invoke(vm, *vm.well_known_symbol_match_all(), js_string(vm, move(string)))); } -// 22.1.3.29 String.prototype.toUpperCase ( ), https://tc39.es/ecma262/#sec-string.prototype.touppercase -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_uppercase) +// 22.1.3.14 String.prototype.normalize ( [ form ] ), https://tc39.es/ecma262/#sec-string.prototype.normalize +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::normalize) { + // 1. Let O be ? RequireObjectCoercible(this value). + // 2. Let S be ? ToString(O). auto string = TRY(ak_string_from(vm)); - auto uppercase = Unicode::to_unicode_uppercase_full(string); - return js_string(vm, move(uppercase)); -} -// 22.1.3.28 String.prototype.toString ( ), https://tc39.es/ecma262/#sec-string.prototype.tostring -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_string) -{ - return TRY(this_string_value(vm, vm.this_value())); -} + // 3. If form is undefined, let f be "NFC". + // 4. Else, let f be ? ToString(form). + String form = "NFC"; + auto form_value = vm.argument(0); + if (!form_value.is_undefined()) + form = TRY(form_value.to_string(vm)); -// 22.1.3.33 String.prototype.valueOf ( ), https://tc39.es/ecma262/#sec-string.prototype.valueof -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::value_of) -{ - return TRY(this_string_value(vm, vm.this_value())); + // 5. If f is not one of "NFC", "NFD", "NFKC", or "NFKD", throw a RangeError exception. + if (!form.is_one_of("NFC"sv, "NFD"sv, "NFKC"sv, "NFKD"sv)) + return vm.throw_completion<RangeError>(ErrorType::InvalidNormalizationForm, form); + + // 6. Let ns be the String value that is the result of normalizing S into the normalization form named by f as specified in https://unicode.org/reports/tr15/. + auto unicode_form = Unicode::normalization_form_from_string(form); + auto ns = Unicode::normalize(string, unicode_form); + + // 7. return ns. + return js_string(vm, move(ns)); } enum class PadPlacement { @@ -470,174 +501,171 @@ static ThrowCompletionOr<Value> pad_string(VM& vm, Utf16String string, PadPlacem return js_string(vm, move(formatted)); } -// 22.1.3.16 String.prototype.padStart ( maxLength [ , fillString ] ), https://tc39.es/ecma262/#sec-string.prototype.padstart -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_start) +// 22.1.3.15 String.prototype.padEnd ( maxLength [ , fillString ] ), https://tc39.es/ecma262/#sec-string.prototype.padend +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_end) { auto string = TRY(utf16_string_from(vm)); - return pad_string(vm, move(string), PadPlacement::Start); + return pad_string(vm, move(string), PadPlacement::End); } -// 22.1.3.15 String.prototype.padEnd ( maxLength [ , fillString ] ), https://tc39.es/ecma262/#sec-string.prototype.padend -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_end) +// 22.1.3.16 String.prototype.padStart ( maxLength [ , fillString ] ), https://tc39.es/ecma262/#sec-string.prototype.padstart +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_start) { auto string = TRY(utf16_string_from(vm)); - return pad_string(vm, move(string), PadPlacement::End); + return pad_string(vm, move(string), PadPlacement::Start); } -ThrowCompletionOr<String> trim_string(VM& vm, Value input_value, TrimMode where) +// 22.1.3.17 String.prototype.repeat ( count ), https://tc39.es/ecma262/#sec-string.prototype.repeat +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat) { - // 1. Let str be ? RequireObjectCoercible(string). - auto input_string = TRY(require_object_coercible(vm, input_value)); + auto string = TRY(ak_string_from(vm)); - // 2. Let S be ? ToString(str). - auto string = TRY(input_string.to_string(vm)); + auto n = TRY(vm.argument(0).to_integer_or_infinity(vm)); - // 3. If where is start, let T be the String value that is a copy of S with leading white space removed. - // 4. Else if where is end, let T be the String value that is a copy of S with trailing white space removed. - // 5. Else, - // a. Assert: where is start+end. - // b. Let T be the String value that is a copy of S with both leading and trailing white space removed. - auto trimmed_string = Utf8View(string).trim(whitespace_characters, where).as_string(); + if (n < 0) + return vm.throw_completion<RangeError>(ErrorType::StringRepeatCountMustBe, "positive"); - // 6. Return T. - return trimmed_string; -} + if (Value(n).is_positive_infinity()) + return vm.throw_completion<RangeError>(ErrorType::StringRepeatCountMustBe, "finite"); -// 22.1.3.30 String.prototype.trim ( ), https://tc39.es/ecma262/#sec-string.prototype.trim -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim) -{ - return js_string(vm, TRY(trim_string(vm, vm.this_value(), TrimMode::Both))); -} + if (n == 0) + return js_string(vm, String::empty()); -// 22.1.3.32 String.prototype.trimStart ( ), https://tc39.es/ecma262/#sec-string.prototype.trimstart -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_start) -{ - return js_string(vm, TRY(trim_string(vm, vm.this_value(), TrimMode::Left))); -} + // NOTE: This is an optimization, it is not required by the specification but it produces equivalent behavior + if (string.is_empty()) + return js_string(vm, String::empty()); -// 22.1.3.31 String.prototype.trimEnd ( ), https://tc39.es/ecma262/#sec-string.prototype.trimend -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_end) -{ - return js_string(vm, TRY(trim_string(vm, vm.this_value(), TrimMode::Right))); + StringBuilder builder; + for (size_t i = 0; i < n; ++i) + builder.append(string); + return js_string(vm, builder.to_string()); } -// 22.1.3.5 String.prototype.concat ( ...args ), https://tc39.es/ecma262/#sec-string.prototype.concat -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::concat) +// 22.1.3.18 String.prototype.replace ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replace +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace) { - // 1. Let O be ? RequireObjectCoercible(this value). - auto object = TRY(require_object_coercible(vm, vm.this_value())); + auto this_object = TRY(require_object_coercible(vm, vm.this_value())); + auto search_value = vm.argument(0); + auto replace_value = vm.argument(1); - // 2. Let S be ? ToString(O). - auto* string = TRY(object.to_primitive_string(vm)); + if (!search_value.is_nullish()) { + if (auto* replacer = TRY(search_value.get_method(vm, *vm.well_known_symbol_replace()))) + return TRY(call(vm, *replacer, search_value, this_object, replace_value)); + } - // 3. Let R be S. - auto* result = string; + auto string = TRY(this_object.to_utf16_string(vm)); + auto search_string = TRY(search_value.to_utf16_string(vm)); - // 4. For each element next of args, do - for (size_t i = 0; i < vm.argument_count(); ++i) { - // a. Let nextString be ? ToString(next). - auto* next_string = TRY(vm.argument(i).to_primitive_string(vm)); + if (!replace_value.is_function()) { + auto replace_string = TRY(replace_value.to_utf16_string(vm)); + replace_value = js_string(vm, move(replace_string)); + } - // b. Set R to the string-concatenation of R and nextString. - result = js_rope_string(vm, *result, *next_string); + Optional<size_t> position = string_index_of(string.view(), search_string.view(), 0); + if (!position.has_value()) + return js_string(vm, move(string)); + + auto preserved = string.substring_view(0, position.value()); + String replacement; + + if (replace_value.is_function()) { + auto result = TRY(call(vm, replace_value.as_function(), js_undefined(), js_string(vm, search_string), Value(position.value()), js_string(vm, string))); + replacement = TRY(result.to_string(vm)); + } else { + replacement = TRY(get_substitution(vm, search_string.view(), string.view(), *position, {}, js_undefined(), replace_value)); } - // 5. Return R. - return result; + StringBuilder builder; + builder.append(preserved); + builder.append(replacement); + builder.append(string.substring_view(*position + search_string.length_in_code_units())); + + return js_string(vm, builder.build()); } -// 22.1.3.24 String.prototype.substring ( start, end ), https://tc39.es/ecma262/#sec-string.prototype.substring -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::substring) +// 22.1.3.19 String.prototype.replaceAll ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replaceall +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all) { - // 1. Let O be ? RequireObjectCoercible(this value). - // 2. Let S be ? ToString(O). - auto string = TRY(utf16_string_from(vm)); + auto this_object = TRY(require_object_coercible(vm, vm.this_value())); + auto search_value = vm.argument(0); + auto replace_value = vm.argument(1); - // 3. Let len be the length of S. - auto string_length = static_cast<double>(string.length_in_code_units()); + if (!search_value.is_nullish()) { + bool is_regexp = TRY(search_value.is_regexp(vm)); - // 4. Let intStart be ? ToIntegerOrInfinity(start). - auto start = TRY(vm.argument(0).to_integer_or_infinity(vm)); - // 5. If end is undefined, let intEnd be len; else let intEnd be ? ToIntegerOrInfinity(end). - auto end = string_length; - if (!vm.argument(1).is_undefined()) - end = TRY(vm.argument(1).to_integer_or_infinity(vm)); + if (is_regexp) { + auto flags = TRY(search_value.as_object().get(vm.names.flags)); + auto flags_object = TRY(require_object_coercible(vm, flags)); + auto flags_string = TRY(flags_object.to_string(vm)); + if (!flags_string.contains('g')) + return vm.throw_completion<TypeError>(ErrorType::StringNonGlobalRegExp); + } - // 6. Let finalStart be the result of clamping intStart between 0 and len. - size_t final_start = clamp(start, static_cast<double>(0), string_length); - // 7. Let finalEnd be the result of clamping intEnd between 0 and len. - size_t final_end = clamp(end, static_cast<double>(0), string_length); + auto* replacer = TRY(search_value.get_method(vm, *vm.well_known_symbol_replace())); + if (replacer) + return TRY(call(vm, *replacer, search_value, this_object, replace_value)); + } - // 8. Let from be min(finalStart, finalEnd). - size_t from = min(final_start, final_end); - // 9. Let to be max(finalStart, finalEnd). - size_t to = max(final_start, final_end); + auto string = TRY(this_object.to_utf16_string(vm)); + auto search_string = TRY(search_value.to_utf16_string(vm)); - // 10. Return the substring of S from from to to. - return js_string(vm, string.substring_view(from, to - from)); -} + if (!replace_value.is_function()) { + auto replace_string = TRY(replace_value.to_utf16_string(vm)); + replace_value = js_string(vm, move(replace_string)); + } -// B.2.2.1 String.prototype.substr ( start, length ), https://tc39.es/ecma262/#sec-string.prototype.substr -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::substr) -{ - // 1. Let O be ? RequireObjectCoercible(this value). - // 2. Let S be ? ToString(O). - auto string = TRY(utf16_string_from(vm)); + auto string_length = string.length_in_code_units(); + auto search_length = search_string.length_in_code_units(); - // 3. Let size be the length of S. - auto size = string.length_in_code_units(); + Vector<size_t> match_positions; + size_t advance_by = max(1u, search_length); + auto position = string_index_of(string.view(), search_string.view(), 0); - // 4. Let intStart be ? ToIntegerOrInfinity(start). - auto int_start = TRY(vm.argument(0).to_integer_or_infinity(vm)); + while (position.has_value()) { + match_positions.append(*position); + position = string_index_of(string.view(), search_string.view(), *position + advance_by); + } - // 5. If intStart is -∞, set intStart to 0. - if (Value(int_start).is_negative_infinity()) - int_start = 0; - // 6. Else if intStart < 0, set intStart to max(size + intStart, 0). - else if (int_start < 0) - int_start = max(size + int_start, 0); - // 7. Else, set intStart to min(intStart, size). - else - int_start = min(int_start, size); + size_t end_of_last_match = 0; + StringBuilder result; - // 8. If length is undefined, let intLength be size; otherwise let intLength be ? ToIntegerOrInfinity(length). - auto length = vm.argument(1); - auto int_length = length.is_undefined() ? size : TRY(length.to_integer_or_infinity(vm)); + for (auto position : match_positions) { + auto preserved = string.substring_view(end_of_last_match, position - end_of_last_match); + String replacement; - // 9. Set intLength to the result of clamping intLength between 0 and size. - int_length = clamp(int_length, 0, size); + if (replace_value.is_function()) { + auto result = TRY(call(vm, replace_value.as_function(), js_undefined(), js_string(vm, search_string), Value(position), js_string(vm, string))); + replacement = TRY(result.to_string(vm)); + } else { + replacement = TRY(get_substitution(vm, search_string.view(), string.view(), position, {}, js_undefined(), replace_value)); + } - // 10. Let intEnd be min(intStart + intLength, size). - auto int_end = min((i32)(int_start + int_length), size); + result.append(preserved); + result.append(replacement); - if (int_start >= int_end) - return js_string(vm, String::empty()); + end_of_last_match = position + search_length; + } - // 11. Return the substring of S from intStart to intEnd. - return js_string(vm, string.substring_view(int_start, int_end - int_start)); + if (end_of_last_match < string_length) + result.append(string.substring_view(end_of_last_match)); + + return js_string(vm, result.build()); } -// 22.1.3.8 String.prototype.includes ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.includes -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::includes) +// 22.1.3.20 String.prototype.search ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.search +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::search) { - auto string = TRY(utf16_string_from(vm)); - - auto search_string_value = vm.argument(0); - - bool search_is_regexp = TRY(search_string_value.is_regexp(vm)); - if (search_is_regexp) - return vm.throw_completion<TypeError>(ErrorType::IsNotA, "searchString", "string, but a regular expression"); - - auto search_string = TRY(search_string_value.to_utf16_string(vm)); - - size_t start = 0; - if (!vm.argument(1).is_undefined()) { - auto position = TRY(vm.argument(1).to_integer_or_infinity(vm)); - start = clamp(position, static_cast<double>(0), static_cast<double>(string.length_in_code_units())); + auto this_object = TRY(require_object_coercible(vm, vm.this_value())); + auto regexp = vm.argument(0); + if (!regexp.is_nullish()) { + if (auto* searcher = TRY(regexp.get_method(vm, *vm.well_known_symbol_search()))) + return TRY(call(vm, *searcher, regexp, this_object)); } - auto index = string_index_of(string.view(), search_string.view(), start); - return Value(index.has_value()); + auto string = TRY(this_object.to_utf16_string(vm)); + + auto rx = TRY(regexp_create(vm, regexp, js_undefined())); + return TRY(Value(rx).invoke(vm, *vm.well_known_symbol_search(), js_string(vm, move(string)))); } // 22.1.3.21 String.prototype.slice ( start, end ), https://tc39.es/ecma262/#sec-string.prototype.slice @@ -739,272 +767,271 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) return array; } -// 22.1.3.10 String.prototype.lastIndexOf ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.lastindexof -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::last_index_of) +// 22.1.3.23 String.prototype.startsWith ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.startswith +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::starts_with) { auto string = TRY(utf16_string_from(vm)); - auto search_string = TRY(vm.argument(0).to_utf16_string(vm)); - auto string_length = string.length_in_code_units(); - auto search_length = search_string.length_in_code_units(); + auto search_string_value = vm.argument(0); - auto position = TRY(vm.argument(1).to_number(vm)); - double pos = position.is_nan() ? static_cast<double>(INFINITY) : TRY(position.to_integer_or_infinity(vm)); + bool search_is_regexp = TRY(search_string_value.is_regexp(vm)); + if (search_is_regexp) + return vm.throw_completion<TypeError>(ErrorType::IsNotA, "searchString", "string, but a regular expression"); - size_t start = clamp(pos, static_cast<double>(0), static_cast<double>(string_length)); - Optional<size_t> last_index; + auto search_string = TRY(search_string_value.to_utf16_string(vm)); + auto string_length = string.length_in_code_units(); + auto search_length = search_string.length_in_code_units(); - for (size_t k = 0; (k <= start) && (k + search_length <= string_length); ++k) { - bool is_match = true; + size_t start = 0; + if (!vm.argument(1).is_undefined()) { + auto position = TRY(vm.argument(1).to_integer_or_infinity(vm)); + start = clamp(position, static_cast<double>(0), static_cast<double>(string_length)); + } - for (size_t j = 0; j < search_length; ++j) { - if (string.code_unit_at(k + j) != search_string.code_unit_at(j)) { - is_match = false; - break; - } - } + if (search_length == 0) + return Value(true); - if (is_match) - last_index = k; - } + size_t end = start + search_length; + if (end > string_length) + return Value(false); - return last_index.has_value() ? Value(*last_index) : Value(-1); + auto substring_view = string.substring_view(start, end - start); + return Value(substring_view == search_string.view()); } -// 22.1.3.1 String.prototype.at ( index ), https://tc39.es/ecma262/#sec-string.prototype.at -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::at) +// 22.1.3.24 String.prototype.substring ( start, end ), https://tc39.es/ecma262/#sec-string.prototype.substring +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::substring) { - // 1. Let O be ? ToObject(this value). + // 1. Let O be ? RequireObjectCoercible(this value). + // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); - // 2. Let len be ? LengthOfArrayLike(O). - auto length = string.length_in_code_units(); - // 3. Let relativeIndex be ? ToIntegerOrInfinity(index). - auto relative_index = TRY(vm.argument(0).to_integer_or_infinity(vm)); - if (Value(relative_index).is_infinity()) - return js_undefined(); + // 3. Let len be the length of S. + auto string_length = static_cast<double>(string.length_in_code_units()); - Checked<size_t> index { 0 }; - // 4. If relativeIndex ≥ 0, then - if (relative_index >= 0) { - // a. Let k be relativeIndex. - index += relative_index; - } - // 5. Else, - else { - // a. Let k be len + relativeIndex. - index += length; - index -= -relative_index; - } - // 6. If k < 0 or k ≥ len, return undefined. - if (index.has_overflow() || index.value() >= length) - return js_undefined(); + // 4. Let intStart be ? ToIntegerOrInfinity(start). + auto start = TRY(vm.argument(0).to_integer_or_infinity(vm)); + // 5. If end is undefined, let intEnd be len; else let intEnd be ? ToIntegerOrInfinity(end). + auto end = string_length; + if (!vm.argument(1).is_undefined()) + end = TRY(vm.argument(1).to_integer_or_infinity(vm)); - // 7. Return ? Get(O, ! ToString(𝔽(k))). - return js_string(vm, string.substring_view(index.value(), 1)); -} + // 6. Let finalStart be the result of clamping intStart between 0 and len. + size_t final_start = clamp(start, static_cast<double>(0), string_length); + // 7. Let finalEnd be the result of clamping intEnd between 0 and len. + size_t final_end = clamp(end, static_cast<double>(0), string_length); -// 22.1.3.34 String.prototype [ @@iterator ] ( ), https://tc39.es/ecma262/#sec-string.prototype-@@iterator -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::symbol_iterator) -{ - auto& realm = *vm.current_realm(); + // 8. Let from be min(finalStart, finalEnd). + size_t from = min(final_start, final_end); + // 9. Let to be max(finalStart, finalEnd). + size_t to = max(final_start, final_end); - auto this_object = TRY(require_object_coercible(vm, vm.this_value())); - auto string = TRY(this_object.to_string(vm)); - return StringIterator::create(realm, string); + // 10. Return the substring of S from from to to. + return js_string(vm, string.substring_view(from, to - from)); } -// 22.1.3.12 String.prototype.match ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.match -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match) -{ - auto this_object = TRY(require_object_coercible(vm, vm.this_value())); - auto regexp = vm.argument(0); - if (!regexp.is_nullish()) { - if (auto* matcher = TRY(regexp.get_method(vm, *vm.well_known_symbol_match()))) - return TRY(call(vm, *matcher, regexp, this_object)); - } +enum class TargetCase { + Lower, + Upper, +}; - auto string = TRY(this_object.to_utf16_string(vm)); +// 19.1.2.1 TransformCase ( S, locales, targetCase ), https://tc39.es/ecma402/#sec-transform-case +static ThrowCompletionOr<String> transform_case(VM& vm, StringView string, Value locales, TargetCase target_case) +{ + // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). + auto requested_locales = TRY(Intl::canonicalize_locale_list(vm, locales)); - auto rx = TRY(regexp_create(vm, regexp, js_undefined())); - return TRY(Value(rx).invoke(vm, *vm.well_known_symbol_match(), js_string(vm, move(string)))); -} + Optional<Locale::LocaleID> requested_locale; -// 22.1.3.13 String.prototype.matchAll ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.matchall -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match_all) -{ - auto this_object = TRY(require_object_coercible(vm, vm.this_value())); - auto regexp = vm.argument(0); - if (!regexp.is_nullish()) { - auto is_regexp = TRY(regexp.is_regexp(vm)); - if (is_regexp) { - auto flags = TRY(regexp.as_object().get("flags")); - auto flags_object = TRY(require_object_coercible(vm, flags)); - auto flags_string = TRY(flags_object.to_string(vm)); - if (!flags_string.contains('g')) - return vm.throw_completion<TypeError>(ErrorType::StringNonGlobalRegExp); - } - if (auto* matcher = TRY(regexp.get_method(vm, *vm.well_known_symbol_match_all()))) - return TRY(call(vm, *matcher, regexp, this_object)); + // 2. If requestedLocales is not an empty List, then + if (!requested_locales.is_empty()) { + // a. Let requestedLocale be requestedLocales[0]. + requested_locale = Locale::parse_unicode_locale_id(requested_locales[0]); + } + // 3. Else, + else { + // a. Let requestedLocale be ! DefaultLocale(). + requested_locale = Locale::parse_unicode_locale_id(Locale::default_locale()); } + VERIFY(requested_locale.has_value()); - auto string = TRY(this_object.to_utf16_string(vm)); + // 4. Let noExtensionsLocale be the String value that is requestedLocale with any Unicode locale extension sequences (6.2.1) removed. + requested_locale->remove_extension_type<Locale::LocaleExtension>(); + auto no_extensions_locale = requested_locale->to_string(); - auto rx = TRY(regexp_create(vm, regexp, js_string(vm, "g"))); - return TRY(Value(rx).invoke(vm, *vm.well_known_symbol_match_all(), js_string(vm, move(string)))); -} + // 5. Let availableLocales be a List with language tags that includes the languages for which the Unicode Character Database contains language sensitive case mappings. Implementations may add additional language tags if they support case mapping for additional locales. + // 6. Let locale be ! BestAvailableLocale(availableLocales, noExtensionsLocale). + auto locale = Intl::best_available_locale(no_extensions_locale); -// 22.1.3.14 String.prototype.normalize ( [ form ] ), https://tc39.es/ecma262/#sec-string.prototype.normalize -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::normalize) -{ - // 1. Let O be ? RequireObjectCoercible(this value). - // 2. Let S be ? ToString(O). - auto string = TRY(ak_string_from(vm)); + // 7. If locale is undefined, set locale to "und". + if (!locale.has_value()) + locale = "und"sv; - // 3. If form is undefined, let f be "NFC". - // 4. Else, let f be ? ToString(form). - String form = "NFC"; - auto form_value = vm.argument(0); - if (!form_value.is_undefined()) - form = TRY(form_value.to_string(vm)); + // 8. Let codePoints be StringToCodePoints(S). - // 5. If f is not one of "NFC", "NFD", "NFKC", or "NFKD", throw a RangeError exception. - if (!form.is_one_of("NFC"sv, "NFD"sv, "NFKC"sv, "NFKD"sv)) - return vm.throw_completion<RangeError>(ErrorType::InvalidNormalizationForm, form); + String new_code_points; - // 6. Let ns be the String value that is the result of normalizing S into the normalization form named by f as specified in https://unicode.org/reports/tr15/. - auto unicode_form = Unicode::normalization_form_from_string(form); - auto ns = Unicode::normalize(string, unicode_form); + switch (target_case) { + // 9. If targetCase is lower, then + case TargetCase::Lower: + // a. Let newCodePoints be a List whose elements are the result of a lowercase transformation of codePoints according to an implementation-derived algorithm using locale or the Unicode Default Case Conversion algorithm. + new_code_points = Unicode::to_unicode_lowercase_full(string, *locale); + break; + // 10. Else, + case TargetCase::Upper: + // a. Assert: targetCase is upper. + // b. Let newCodePoints be a List whose elements are the result of an uppercase transformation of codePoints according to an implementation-derived algorithm using locale or the Unicode Default Case Conversion algorithm. + new_code_points = Unicode::to_unicode_uppercase_full(string, *locale); + break; + default: + VERIFY_NOT_REACHED(); + } - // 7. return ns. - return js_string(vm, move(ns)); + // 11. Return CodePointsToString(newCodePoints). + return new_code_points; } -// 22.1.3.18 String.prototype.replace ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replace -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace) +// 22.1.3.25 String.prototype.toLocaleLowerCase ( [ reserved1 [ , reserved2 ] ] ), https://tc39.es/ecma262/#sec-string.prototype.tolocalelowercase +// 19.1.2 String.prototype.toLocaleLowerCase ( [ locales ] ), https://tc39.es/ecma402/#sup-string.prototype.tolocalelowercase +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_lowercase) { - auto this_object = TRY(require_object_coercible(vm, vm.this_value())); - auto search_value = vm.argument(0); - auto replace_value = vm.argument(1); + auto locales = vm.argument(0); - if (!search_value.is_nullish()) { - if (auto* replacer = TRY(search_value.get_method(vm, *vm.well_known_symbol_replace()))) - return TRY(call(vm, *replacer, search_value, this_object, replace_value)); - } + // 1. Let O be ? RequireObjectCoercible(this value). + // 2. Let S be ? ToString(O). + auto string = TRY(ak_string_from(vm)); - auto string = TRY(this_object.to_utf16_string(vm)); - auto search_string = TRY(search_value.to_utf16_string(vm)); + // 3. Return ? TransformCase(S, locales, lower). + return js_string(vm, TRY(transform_case(vm, string, locales, TargetCase::Lower))); +} - if (!replace_value.is_function()) { - auto replace_string = TRY(replace_value.to_utf16_string(vm)); - replace_value = js_string(vm, move(replace_string)); - } +// 22.1.3.26 String.prototype.toLocaleUpperCase ( [ reserved1 [ , reserved2 ] ] ), https://tc39.es/ecma262/#sec-string.prototype.tolocaleuppercase +// 19.1.3 String.prototype.toLocaleUpperCase ( [ locales ] ), https://tc39.es/ecma402/#sup-string.prototype.tolocaleuppercase +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_uppercase) +{ + auto locales = vm.argument(0); - Optional<size_t> position = string_index_of(string.view(), search_string.view(), 0); - if (!position.has_value()) - return js_string(vm, move(string)); + // 1. Let O be ? RequireObjectCoercible(this value). + // 2. Let S be ? ToString(O). + auto string = TRY(ak_string_from(vm)); - auto preserved = string.substring_view(0, position.value()); - String replacement; + // 3. Return ? TransformCase(S, locales, upper). + return js_string(vm, TRY(transform_case(vm, string, locales, TargetCase::Upper))); +} - if (replace_value.is_function()) { - auto result = TRY(call(vm, replace_value.as_function(), js_undefined(), js_string(vm, search_string), Value(position.value()), js_string(vm, string))); - replacement = TRY(result.to_string(vm)); - } else { - replacement = TRY(get_substitution(vm, search_string.view(), string.view(), *position, {}, js_undefined(), replace_value)); - } +// 22.1.3.27 String.prototype.toLowerCase ( ), https://tc39.es/ecma262/#sec-string.prototype.tolowercase +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_lowercase) +{ + auto string = TRY(ak_string_from(vm)); + auto lowercase = Unicode::to_unicode_lowercase_full(string); + return js_string(vm, move(lowercase)); +} - StringBuilder builder; - builder.append(preserved); - builder.append(replacement); - builder.append(string.substring_view(*position + search_string.length_in_code_units())); +// 22.1.3.28 String.prototype.toString ( ), https://tc39.es/ecma262/#sec-string.prototype.tostring +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_string) +{ + return TRY(this_string_value(vm, vm.this_value())); +} - return js_string(vm, builder.build()); +// 22.1.3.29 String.prototype.toUpperCase ( ), https://tc39.es/ecma262/#sec-string.prototype.touppercase +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_uppercase) +{ + auto string = TRY(ak_string_from(vm)); + auto uppercase = Unicode::to_unicode_uppercase_full(string); + return js_string(vm, move(uppercase)); } -// 22.1.3.19 String.prototype.replaceAll ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replaceall -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all) +ThrowCompletionOr<String> trim_string(VM& vm, Value input_value, TrimMode where) { - auto this_object = TRY(require_object_coercible(vm, vm.this_value())); - auto search_value = vm.argument(0); - auto replace_value = vm.argument(1); + // 1. Let str be ? RequireObjectCoercible(string). + auto input_string = TRY(require_object_coercible(vm, input_value)); - if (!search_value.is_nullish()) { - bool is_regexp = TRY(search_value.is_regexp(vm)); + // 2. Let S be ? ToString(str). + auto string = TRY(input_string.to_string(vm)); - if (is_regexp) { - auto flags = TRY(search_value.as_object().get(vm.names.flags)); - auto flags_object = TRY(require_object_coercible(vm, flags)); - auto flags_string = TRY(flags_object.to_string(vm)); - if (!flags_string.contains('g')) - return vm.throw_completion<TypeError>(ErrorType::StringNonGlobalRegExp); - } + // 3. If where is start, let T be the String value that is a copy of S with leading white space removed. + // 4. Else if where is end, let T be the String value that is a copy of S with trailing white space removed. + // 5. Else, + // a. Assert: where is start+end. + // b. Let T be the String value that is a copy of S with both leading and trailing white space removed. + auto trimmed_string = Utf8View(string).trim(whitespace_characters, where).as_string(); - auto* replacer = TRY(search_value.get_method(vm, *vm.well_known_symbol_replace())); - if (replacer) - return TRY(call(vm, *replacer, search_value, this_object, replace_value)); - } + // 6. Return T. + return trimmed_string; +} - auto string = TRY(this_object.to_utf16_string(vm)); - auto search_string = TRY(search_value.to_utf16_string(vm)); +// 22.1.3.30 String.prototype.trim ( ), https://tc39.es/ecma262/#sec-string.prototype.trim +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim) +{ + return js_string(vm, TRY(trim_string(vm, vm.this_value(), TrimMode::Both))); +} - if (!replace_value.is_function()) { - auto replace_string = TRY(replace_value.to_utf16_string(vm)); - replace_value = js_string(vm, move(replace_string)); - } +// 22.1.3.31 String.prototype.trimEnd ( ), https://tc39.es/ecma262/#sec-string.prototype.trimend +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_end) +{ + return js_string(vm, TRY(trim_string(vm, vm.this_value(), TrimMode::Right))); +} - auto string_length = string.length_in_code_units(); - auto search_length = search_string.length_in_code_units(); +// 22.1.3.32 String.prototype.trimStart ( ), https://tc39.es/ecma262/#sec-string.prototype.trimstart +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_start) +{ + return js_string(vm, TRY(trim_string(vm, vm.this_value(), TrimMode::Left))); +} - Vector<size_t> match_positions; - size_t advance_by = max(1u, search_length); - auto position = string_index_of(string.view(), search_string.view(), 0); +// 22.1.3.33 String.prototype.valueOf ( ), https://tc39.es/ecma262/#sec-string.prototype.valueof +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::value_of) +{ + return TRY(this_string_value(vm, vm.this_value())); +} - while (position.has_value()) { - match_positions.append(*position); - position = string_index_of(string.view(), search_string.view(), *position + advance_by); - } +// 22.1.3.34 String.prototype [ @@iterator ] ( ), https://tc39.es/ecma262/#sec-string.prototype-@@iterator +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::symbol_iterator) +{ + auto& realm = *vm.current_realm(); - size_t end_of_last_match = 0; - StringBuilder result; + auto this_object = TRY(require_object_coercible(vm, vm.this_value())); + auto string = TRY(this_object.to_string(vm)); + return StringIterator::create(realm, string); +} - for (auto position : match_positions) { - auto preserved = string.substring_view(end_of_last_match, position - end_of_last_match); - String replacement; +// B.2.2.1 String.prototype.substr ( start, length ), https://tc39.es/ecma262/#sec-string.prototype.substr +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::substr) +{ + // 1. Let O be ? RequireObjectCoercible(this value). + // 2. Let S be ? ToString(O). + auto string = TRY(utf16_string_from(vm)); - if (replace_value.is_function()) { - auto result = TRY(call(vm, replace_value.as_function(), js_undefined(), js_string(vm, search_string), Value(position), js_string(vm, string))); - replacement = TRY(result.to_string(vm)); - } else { - replacement = TRY(get_substitution(vm, search_string.view(), string.view(), position, {}, js_undefined(), replace_value)); - } + // 3. Let size be the length of S. + auto size = string.length_in_code_units(); - result.append(preserved); - result.append(replacement); + // 4. Let intStart be ? ToIntegerOrInfinity(start). + auto int_start = TRY(vm.argument(0).to_integer_or_infinity(vm)); - end_of_last_match = position + search_length; - } + // 5. If intStart is -∞, set intStart to 0. + if (Value(int_start).is_negative_infinity()) + int_start = 0; + // 6. Else if intStart < 0, set intStart to max(size + intStart, 0). + else if (int_start < 0) + int_start = max(size + int_start, 0); + // 7. Else, set intStart to min(intStart, size). + else + int_start = min(int_start, size); - if (end_of_last_match < string_length) - result.append(string.substring_view(end_of_last_match)); + // 8. If length is undefined, let intLength be size; otherwise let intLength be ? ToIntegerOrInfinity(length). + auto length = vm.argument(1); + auto int_length = length.is_undefined() ? size : TRY(length.to_integer_or_infinity(vm)); - return js_string(vm, result.build()); -} + // 9. Set intLength to the result of clamping intLength between 0 and size. + int_length = clamp(int_length, 0, size); -// 22.1.3.20 String.prototype.search ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.search -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::search) -{ - auto this_object = TRY(require_object_coercible(vm, vm.this_value())); - auto regexp = vm.argument(0); - if (!regexp.is_nullish()) { - if (auto* searcher = TRY(regexp.get_method(vm, *vm.well_known_symbol_search()))) - return TRY(call(vm, *searcher, regexp, this_object)); - } + // 10. Let intEnd be min(intStart + intLength, size). + auto int_end = min((i32)(int_start + int_length), size); - auto string = TRY(this_object.to_utf16_string(vm)); + if (int_start >= int_end) + return js_string(vm, String::empty()); - auto rx = TRY(regexp_create(vm, regexp, js_undefined())); - return TRY(Value(rx).invoke(vm, *vm.well_known_symbol_search(), js_string(vm, move(string)))); + // 11. Return the substring of S from intStart to intEnd. + return js_string(vm, string.substring_view(int_start, int_end - int_start)); } // B.2.2.2.1 CreateHTML ( string, tag, attribute, value ), https://tc39.es/ecma262/#sec-createhtml @@ -1109,26 +1136,4 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::sup) return create_html(vm, vm.this_value(), "sup", String::empty(), Value()); } -// 22.1.3.11 String.prototype.localeCompare ( that [ , reserved1 [ , reserved2 ] ] ), https://tc39.es/ecma262/#sec-string.prototype.localecompare -// 19.1.1 String.prototype.localeCompare ( that [ , locales [ , options ] ] ), https://tc39.es/ecma402/#sup-String.prototype.localeCompare -JS_DEFINE_NATIVE_FUNCTION(StringPrototype::locale_compare) -{ - auto& realm = *vm.current_realm(); - - // 1. Let O be ? RequireObjectCoercible(this value). - auto object = TRY(require_object_coercible(vm, vm.this_value())); - - // 2. Let S be ? ToString(O). - auto string = TRY(object.to_string(vm)); - - // 3. Let thatValue be ? ToString(that). - auto that_value = TRY(vm.argument(0).to_string(vm)); - - // 4. Let collator be ? Construct(%Collator%, « locales, options »). - auto* collator = TRY(construct(vm, *realm.intrinsics().intl_collator_constructor(), vm.argument(1), vm.argument(2))); - - // 5. Return CompareStrings(collator, S, thatValue). - return Intl::compare_strings(static_cast<Intl::Collator&>(*collator), Utf8View(string), Utf8View(that_value)); -} - } diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.h b/Userland/Libraries/LibJS/Runtime/StringPrototype.h index afbbda62d0..96d1b2fd61 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.h @@ -30,38 +30,41 @@ public: virtual ~StringPrototype() override = default; private: + JS_DECLARE_NATIVE_FUNCTION(at); JS_DECLARE_NATIVE_FUNCTION(char_at); JS_DECLARE_NATIVE_FUNCTION(char_code_at); JS_DECLARE_NATIVE_FUNCTION(code_point_at); - JS_DECLARE_NATIVE_FUNCTION(repeat); - JS_DECLARE_NATIVE_FUNCTION(starts_with); - JS_DECLARE_NATIVE_FUNCTION(ends_with); - JS_DECLARE_NATIVE_FUNCTION(index_of); - JS_DECLARE_NATIVE_FUNCTION(to_locale_lowercase); - JS_DECLARE_NATIVE_FUNCTION(to_locale_uppercase); - JS_DECLARE_NATIVE_FUNCTION(to_lowercase); - JS_DECLARE_NATIVE_FUNCTION(to_uppercase); - JS_DECLARE_NATIVE_FUNCTION(to_string); - JS_DECLARE_NATIVE_FUNCTION(value_of); - JS_DECLARE_NATIVE_FUNCTION(pad_start); - JS_DECLARE_NATIVE_FUNCTION(pad_end); - JS_DECLARE_NATIVE_FUNCTION(substring); - JS_DECLARE_NATIVE_FUNCTION(substr); - JS_DECLARE_NATIVE_FUNCTION(trim); - JS_DECLARE_NATIVE_FUNCTION(trim_start); - JS_DECLARE_NATIVE_FUNCTION(trim_end); JS_DECLARE_NATIVE_FUNCTION(concat); + JS_DECLARE_NATIVE_FUNCTION(ends_with); JS_DECLARE_NATIVE_FUNCTION(includes); - JS_DECLARE_NATIVE_FUNCTION(slice); - JS_DECLARE_NATIVE_FUNCTION(split); + JS_DECLARE_NATIVE_FUNCTION(index_of); JS_DECLARE_NATIVE_FUNCTION(last_index_of); - JS_DECLARE_NATIVE_FUNCTION(at); + JS_DECLARE_NATIVE_FUNCTION(locale_compare); JS_DECLARE_NATIVE_FUNCTION(match); JS_DECLARE_NATIVE_FUNCTION(match_all); JS_DECLARE_NATIVE_FUNCTION(normalize); + JS_DECLARE_NATIVE_FUNCTION(pad_end); + JS_DECLARE_NATIVE_FUNCTION(pad_start); + JS_DECLARE_NATIVE_FUNCTION(repeat); JS_DECLARE_NATIVE_FUNCTION(replace); JS_DECLARE_NATIVE_FUNCTION(replace_all); JS_DECLARE_NATIVE_FUNCTION(search); + JS_DECLARE_NATIVE_FUNCTION(slice); + JS_DECLARE_NATIVE_FUNCTION(split); + JS_DECLARE_NATIVE_FUNCTION(starts_with); + JS_DECLARE_NATIVE_FUNCTION(substring); + JS_DECLARE_NATIVE_FUNCTION(to_locale_lowercase); + JS_DECLARE_NATIVE_FUNCTION(to_locale_uppercase); + JS_DECLARE_NATIVE_FUNCTION(to_lowercase); + JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(to_uppercase); + JS_DECLARE_NATIVE_FUNCTION(trim); + JS_DECLARE_NATIVE_FUNCTION(trim_end); + JS_DECLARE_NATIVE_FUNCTION(trim_start); + JS_DECLARE_NATIVE_FUNCTION(value_of); + JS_DECLARE_NATIVE_FUNCTION(symbol_iterator); + + JS_DECLARE_NATIVE_FUNCTION(substr); JS_DECLARE_NATIVE_FUNCTION(anchor); JS_DECLARE_NATIVE_FUNCTION(big); JS_DECLARE_NATIVE_FUNCTION(blink); @@ -75,9 +78,6 @@ private: JS_DECLARE_NATIVE_FUNCTION(strike); JS_DECLARE_NATIVE_FUNCTION(sub); JS_DECLARE_NATIVE_FUNCTION(sup); - JS_DECLARE_NATIVE_FUNCTION(locale_compare); - - JS_DECLARE_NATIVE_FUNCTION(symbol_iterator); }; } |