/* * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2020-2023, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace JS { GC_DEFINE_ALLOCATOR(StringPrototype); static ThrowCompletionOr utf8_string_from(VM& vm) { auto this_value = TRY(require_object_coercible(vm, vm.this_value())); return TRY(this_value.to_string(vm)); } static ThrowCompletionOr utf16_string_from(VM& vm) { auto this_value = TRY(require_object_coercible(vm, vm.this_value())); return TRY(this_value.to_utf16_string(vm)); } // 22.1.3.21.1 SplitMatch ( S, q, R ), https://tc39.es/ecma262/#sec-splitmatch // FIXME: This no longer exists in the spec! static Optional split_match(Utf16View const& haystack, size_t start, Utf16View const& needle) { auto r = needle.length_in_code_units(); auto s = haystack.length_in_code_units(); if (start + r > s) return {}; for (size_t i = 0; i < r; ++i) { if (haystack.code_unit_at(start + i) != needle.code_unit_at(i)) return {}; } return start + r; } // 6.1.4.1 StringIndexOf ( string, searchValue, fromIndex ), https://tc39.es/ecma262/#sec-stringindexof Optional string_index_of(Utf16View const& string, Utf16View const& search_value, size_t from_index) { // 1. Let len be the length of string. size_t string_length = string.length_in_code_units(); // 3. Let searchLen be the length of searchValue. size_t search_length = search_value.length_in_code_units(); // 2. If searchValue is the empty String and fromIndex ≤ len, return fromIndex. if ((search_length == 0) && (from_index <= string_length)) return from_index; // OPTIMIZATION: If the needle is longer than the haystack, don't bother searching :^) if (search_length > string_length) return {}; // 4. For each integer i such that fromIndex ≤ i ≤ len - searchLen, in ascending order, do for (size_t i = from_index; i <= string_length - search_length; ++i) { // a. Let candidate be the substring of string from i to i + searchLen. auto candidate = string.substring_view(i, search_length); // b. If candidate is searchValue, return i. if (candidate == search_value) return i; } // 5. Return -1. return {}; } // 7.2.9 Static Semantics: IsStringWellFormedUnicode ( string ) static bool is_string_well_formed_unicode(Utf16View string) { // OPTIMIZATION: simdutf can do this much faster. return string.validate(); } // 11.1.4 CodePointAt ( string, position ), https://tc39.es/ecma262/#sec-codepointat CodePoint code_point_at(Utf16View const& string, size_t position) { // 1. Let size be the length of string. // 2. Assert: position ≥ 0 and position < size. VERIFY(position < string.length_in_code_units()); // 3. Let first be the code unit at index position within string. auto first = string.code_unit_at(position); // 4. Let cp be the code point whose numeric value is that of first. auto code_point = static_cast(first); // 5. If first is not a leading surrogate or trailing surrogate, then if (!is_unicode_surrogate(first)) { // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }. return { false, code_point, 1 }; } // 6. If first is a trailing surrogate or position + 1 = size, then if (Utf16View::is_low_surrogate(first) || (position + 1 == string.length_in_code_units())) { // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }. return { true, code_point, 1 }; } // 7. Let second be the code unit at index position + 1 within string. auto second = string.code_unit_at(position + 1); // 8. If second is not a trailing surrogate, then if (!Utf16View::is_low_surrogate(second)) { // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }. return { true, code_point, 1 }; } // 9. Set cp to UTF16SurrogatePairToCodePoint(first, second). code_point = Utf16View::decode_surrogate_pair(first, second); // 10. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }. return { false, code_point, 2 }; } StringPrototype::StringPrototype(Realm& realm) : StringObject(*PrimitiveString::create(realm.vm(), String {}), realm.intrinsics().object_prototype()) { } void StringPrototype::initialize(Realm& realm) { auto& vm = this->vm(); Base::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.concat, concat, 1, 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.indexOf, index_of, 1, attr); define_native_function(realm, vm.names.isWellFormed, is_well_formed, 0, attr); define_native_function(realm, vm.names.lastIndexOf, last_index_of, 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.toWellFormed, to_well_formed, 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); define_native_function(realm, vm.names.bold, bold, 0, attr); define_native_function(realm, vm.names.fixed, fixed, 0, attr); define_native_function(realm, vm.names.fontcolor, fontcolor, 1, attr); define_native_function(realm, vm.names.fontsize, fontsize, 1, attr); define_native_function(realm, vm.names.italics, italics, 0, attr); define_native_function(realm, vm.names.link, link, 1, attr); define_native_function(realm, vm.names.small, small, 0, attr); 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_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 static ThrowCompletionOr> this_string_value(VM& vm, Value value) { // 1. If value is a String, return value. if (value.is_string()) return value.as_string(); // 2. If value is an Object and value has a [[StringData]] internal slot, then if (value.is_object() && is(value.as_object())) { // a. Let s be value.[[StringData]]. // b. Assert: s is a String. // c. Return s. return static_cast(value.as_object()).primitive_string(); } // 3. Throw a TypeError exception. return vm.throw_completion(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 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 PrimitiveString::create(vm, Utf16String::create(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) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Let position be ? ToIntegerOrInfinity(pos). auto position = TRY(vm.argument(0).to_integer_or_infinity(vm)); // 4. Let size be the length of S. // 5. If position < 0 or position ≥ size, return the empty String. if (position < 0 || position >= string.length_in_code_units()) return PrimitiveString::create(vm, String {}); // 6. Return the substring of S from position to position + 1. return PrimitiveString::create(vm, Utf16String::create(string.substring_view(position, 1))); } // 22.1.3.3 String.prototype.charCodeAt ( pos ), https://tc39.es/ecma262/#sec-string.prototype.charcodeat JS_DEFINE_NATIVE_FUNCTION(StringPrototype::char_code_at) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Let position be ? ToIntegerOrInfinity(pos). auto position = TRY(vm.argument(0).to_integer_or_infinity(vm)); // 4. Let size be the length of S. // 5. If position < 0 or position ≥ size, return NaN. if (position < 0 || position >= string.length_in_code_units()) return js_nan(); // 6. Return the Number value for the numeric value of the code unit at index position within the String S. return Value(string.code_unit_at(position)); } // 22.1.3.4 String.prototype.codePointAt ( pos ), https://tc39.es/ecma262/#sec-string.prototype.codepointat JS_DEFINE_NATIVE_FUNCTION(StringPrototype::code_point_at) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Let position be ? ToIntegerOrInfinity(pos). auto position = TRY(vm.argument(0).to_integer_or_infinity(vm)); // 4. Let size be the length of S. // 5. If position < 0 or position ≥ size, return undefined. if (position < 0 || position >= string.length_in_code_units()) return js_undefined(); // 6. Let cp be CodePointAt(S, position). auto code_point = JS::code_point_at(string.view(), position); // 7. Return 𝔽(cp.[[CodePoint]]). return Value(code_point.code_point); } // 22.1.3.5 String.prototype.concat ( ...args ), https://tc39.es/ecma262/#sec-string.prototype.concat JS_DEFINE_NATIVE_FUNCTION(StringPrototype::concat) { // 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_primitive_string(vm)); // 3. Let R be S. auto result = string; // 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)); // b. Set R to the string-concatenation of R and nextString. result = PrimitiveString::create(vm, *result, *next_string); } // 5. Return R. return result; } // 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 search_string_value = vm.argument(0); auto end_position = vm.argument(1); // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // Let isRegExp be ? IsRegExp(searchString). bool is_regexp = TRY(search_string_value.is_regexp(vm)); // 4. If isRegExp is true, throw a TypeError exception. if (is_regexp) return vm.throw_completion(ErrorType::IsNotA, "searchString", "string, but a regular expression"); // 5. Let searchStr be ? ToString(searchString). auto search_string = TRY(search_string_value.to_utf16_string(vm)); // 6. Let len be the length of S. auto string_length = string.length_in_code_units(); // 7. If endPosition is undefined, let pos be len; else let pos be ? ToIntegerOrInfinity(endPosition). size_t end = string_length; if (!end_position.is_undefined()) { auto position = TRY(end_position.to_integer_or_infinity(vm)); // 8. Let end be the result of clamping pos between 0 and len. end = clamp(position, static_cast(0), static_cast(string_length)); } // 9. Let searchLength be the length of searchStr. auto search_length = search_string.length_in_code_units(); // 10. If searchLength = 0, return true. if (search_length == 0) return Value(true); // 12. If start < 0, return false. if (search_length > end) return Value(false); // 11. Let start be end - searchLength. size_t start = end - search_length; // 13. Let substring be the substring of S from start to end. auto substring_view = string.substring_view(start, end - start); // 14. If substring is searchStr, return true. // 15. Return false. return Value(substring_view == search_string.view()); } // 22.1.3.8 String.prototype.includes ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.includes JS_DEFINE_NATIVE_FUNCTION(StringPrototype::includes) { auto search_string_value = vm.argument(0); auto position = vm.argument(1); // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Let isRegExp be ? IsRegExp(searchString). bool is_regexp = TRY(search_string_value.is_regexp(vm)); // 4. If isRegExp is true, throw a TypeError exception. if (is_regexp) return vm.throw_completion(ErrorType::IsNotA, "searchString", "string, but a regular expression"); // 5. Let searchStr be ? ToString(searchString). auto search_string = TRY(search_string_value.to_utf16_string(vm)); size_t start = 0; if (!position.is_undefined()) { // 6. Let pos be ? ToIntegerOrInfinity(position). // 7. Assert: If position is undefined, then pos is 0. auto pos = TRY(position.to_integer_or_infinity(vm)); // 8. Let len be the length of S. // 9. Let start be the result of clamping pos between 0 and len. start = clamp(pos, static_cast(0), static_cast(string.length_in_code_units())); } // 10. Let index be StringIndexOf(S, searchStr, start). auto index = string_index_of(string.view(), search_string.view(), start); // 11. If index ≠ -1, return true. // 12. Return false. return Value(index.has_value()); } // 22.1.3.9 String.prototype.indexOf ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.indexof JS_DEFINE_NATIVE_FUNCTION(StringPrototype::index_of) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Let searchStr be ? ToString(searchString). auto search_string = TRY(vm.argument(0).to_utf16_string(vm)); auto utf16_string_view = string.view(); auto utf16_search_view = search_string.view(); size_t start = 0; if (vm.argument_count() > 1) { // 4. Let pos be ? ToIntegerOrInfinity(position). // 5. Assert: If position is undefined, then pos is 0. auto position = TRY(vm.argument(1).to_integer_or_infinity(vm)); // 6. Let len be the length of S. // 7. Let start be the result of clamping pos between 0 and len. start = clamp(position, static_cast(0), static_cast(utf16_string_view.length_in_code_units())); } // 8. Return 𝔽(StringIndexOf(S, searchStr, start)). auto index = string_index_of(utf16_string_view, utf16_search_view, start); return index.has_value() ? Value(*index) : Value(-1); } // 22.1.3.10 String.prototype.isWellFormed ( ), https://tc39.es/ecma262/#sec-string.prototype.iswellformed JS_DEFINE_NATIVE_FUNCTION(StringPrototype::is_well_formed) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Return IsStringWellFormedUnicode(S). return is_string_well_formed_unicode(string.view()); } // 22.1.3.11 String.prototype.lastIndexOf ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.lastindexof JS_DEFINE_NATIVE_FUNCTION(StringPrototype::last_index_of) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Let searchStr be ? ToString(searchString). auto search_string = TRY(vm.argument(0).to_utf16_string(vm)); // 4. Let numPos be ? ToNumber(position). // 5. Assert: If position is undefined, then numPos is NaN. auto position = TRY(vm.argument(1).to_number(vm)); // 6. If numPos is NaN, let pos be +∞; otherwise, let pos be ! ToIntegerOrInfinity(numPos). double pos = position.is_nan() ? static_cast(INFINITY) : MUST(position.to_integer_or_infinity(vm)); // 7. Let len be the length of S. auto string_length = string.length_in_code_units(); // 8. Let searchLen be the length of searchStr. auto search_length = search_string.length_in_code_units(); // 9. Let start be the result of clamping pos between 0 and len - searchLen. size_t start = clamp(pos, static_cast(0), static_cast(string_length)); Optional last_index; // 10. If searchStr is the empty String, return 𝔽(start). // 11. For each integer i such that 0 ≤ i ≤ start, in descending order, do for (size_t k = 0; (k <= start) && (k + search_length <= string_length); ++k) { bool is_match = true; // a. Let candidate be the substring of S from i to i + searchLen. 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; } } // b. If candidate is searchStr, return 𝔽(i). if (is_match) last_index = k; } // 12. Return -1𝔽. return last_index.has_value() ? Value(*last_index) : Value(-1); } // 22.1.3.12 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(*collator), string, that_value); } // 22.1.3.13 String.prototype.match ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.match JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match) { // 1. Let O be ? RequireObjectCoercible(this value). auto this_object = TRY(require_object_coercible(vm, vm.this_value())); // 2. If regexp is neither undefined nor null, then auto regexp = vm.argument(0); if (!regexp.is_nullish()) { // a. Let matcher be ? GetMethod(regexp, @@match). auto matcher = TRY(regexp.get_method(vm, vm.well_known_symbol_match())); // b. If matcher is not undefined, then if (matcher) { // i. Return ? Call(matcher, regexp, « O »). return TRY(call(vm, *matcher, regexp, this_object)); } } // 3. Let S be ? ToString(O). auto string = TRY(this_object.to_utf16_string(vm)); // 4. Let rx be ? RegExpCreate(regexp, undefined). auto rx = TRY(regexp_create(vm, regexp, js_undefined())); // 5. Return ? Invoke(rx, @@match, « S »). return TRY(Value(rx).invoke(vm, vm.well_known_symbol_match(), PrimitiveString::create(vm, move(string)))); } // 22.1.3.14 String.prototype.matchAll ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.matchall JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match_all) { auto regexp = vm.argument(0); // 1. Let O be ? RequireObjectCoercible(this value). auto this_object = TRY(require_object_coercible(vm, vm.this_value())); // 2. If regexp is neither undefined nor null, then if (!regexp.is_nullish()) { // a. Let isRegExp be ? IsRegExp(regexp). auto is_regexp = TRY(regexp.is_regexp(vm)); // b. If isRegExp is true, then if (is_regexp) { // i. Let flags be ? Get(regexp, "flags"). auto flags = TRY(regexp.as_object().get("flags")); // ii. Perform ? RequireObjectCoercible(flags). auto flags_object = TRY(require_object_coercible(vm, flags)); // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. auto flags_string = TRY(flags_object.to_string(vm)); if (!flags_string.contains('g')) return vm.throw_completion(ErrorType::StringNonGlobalRegExp); } // c. Let matcher be ? GetMethod(regexp, @@matchAll). auto matcher = TRY(regexp.get_method(vm, vm.well_known_symbol_match_all())); // d. If matcher is not undefined, then if (matcher) { // i. Return ? Call(matcher, regexp, « O »). return TRY(call(vm, *matcher, regexp, this_object)); } } // 3. Let S be ? ToString(O). auto string = TRY(this_object.to_utf16_string(vm)); // 4. Let rx be ? RegExpCreate(regexp, "g"). auto rx = TRY(regexp_create(vm, regexp, PrimitiveString::create(vm, "g"_string))); // 5. Return ? Invoke(rx, @@matchAll, « S »). return TRY(Value(rx).invoke(vm, vm.well_known_symbol_match_all(), PrimitiveString::create(vm, move(string)))); } // 22.1.3.15 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(utf8_string_from(vm)); String form; // 3. If form is undefined, let f be "NFC". if (auto form_value = vm.argument(0); form_value.is_undefined()) { form = "NFC"_string; } // 4. Else, let f be ? ToString(form). else { form = TRY(form_value.to_string(vm)); } // 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(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 PrimitiveString::create(vm, move(ns)); } enum class PadPlacement { Start, End, }; // 22.1.3.17.1 StringPad ( O, maxLength, fillString, placement ), https://tc39.es/ecma262/#sec-stringpad static ThrowCompletionOr pad_string(VM& vm, Utf16String string, Value max_length, Value fill_string, PadPlacement placement) { // 1. Let S be ? ToString(O). // 2. Let intMaxLength be ℝ(? ToLength(maxLength)). auto int_max_length = TRY(max_length.to_length(vm)); // 3. Let stringLength be the length of S. auto string_length = string.length_in_code_units(); // 4. If intMaxLength ≤ stringLength, return S. if (int_max_length <= string_length) return PrimitiveString::create(vm, move(string)); // 5. If fillString is undefined, let filler be the String value consisting solely of the code unit 0x0020 (SPACE). auto filler = Utf16String::create(Utf16Data { 0x20 }); if (!fill_string.is_undefined()) { // 6. Else, let filler be ? ToString(fillString). filler = TRY(fill_string.to_utf16_string(vm)); // 7. If filler is the empty String, return S. if (filler.is_empty()) return PrimitiveString::create(vm, move(string)); } // 8. Let fillLen be intMaxLength - stringLength. auto fill_length = int_max_length - string_length; StringBuilder truncated_string_filler_builder; auto fill_code_units = filler.length_in_code_units(); for (size_t i = 0; i < fill_length / fill_code_units; ++i) truncated_string_filler_builder.append(filler.view()); // 9. Let truncatedStringFiller be the String value consisting of repeated concatenations of filler truncated to length fillLen. truncated_string_filler_builder.append(filler.substring_view(0, fill_length % fill_code_units)); auto truncated_string_filler = MUST(truncated_string_filler_builder.to_string()); // 10. If placement is start, return the string-concatenation of truncatedStringFiller and S. // 11. Else, return the string-concatenation of S and truncatedStringFiller. auto formatted = placement == PadPlacement::Start ? MUST(String::formatted("{}{}", truncated_string_filler, string.view())) : MUST(String::formatted("{}{}", string.view(), truncated_string_filler)); return PrimitiveString::create(vm, move(formatted)); } // 22.1.3.16 String.prototype.padEnd ( maxLength [ , fillString ] ), https://tc39.es/ecma262/#sec-string.prototype.padend JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_end) { auto max_length = vm.argument(0); auto fill_string = vm.argument(1); // 1. Let O be ? RequireObjectCoercible(this value). auto string = TRY(utf16_string_from(vm)); // 2. Return ? StringPad(O, maxLength, fillString, end). return pad_string(vm, move(string), max_length, fill_string, PadPlacement::End); } // 22.1.3.17 String.prototype.padStart ( maxLength [ , fillString ] ), https://tc39.es/ecma262/#sec-string.prototype.padstart JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_start) { auto max_length = vm.argument(0); auto fill_string = vm.argument(1); // 1. Let O be ? RequireObjectCoercible(this value). auto string = TRY(utf16_string_from(vm)); // 2. Return ? StringPad(O, maxLength, fillString, start). return pad_string(vm, move(string), max_length, fill_string, PadPlacement::Start); } // 22.1.3.18 String.prototype.repeat ( count ), https://tc39.es/ecma262/#sec-string.prototype.repeat JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf8_string_from(vm)); // 3. Let n be ? ToIntegerOrInfinity(count). auto n = TRY(vm.argument(0).to_integer_or_infinity(vm)); // 4. If n < 0 or n = +∞, throw a RangeError exception. if (n < 0) return vm.throw_completion(ErrorType::StringRepeatCountMustBe, "positive"); if (Value(n).is_positive_infinity()) return vm.throw_completion(ErrorType::StringRepeatCountMustBe, "finite"); // 5. If n = 0, return the empty String. if (n == 0) return PrimitiveString::create(vm, String {}); // OPTIMIZATION: If the string is empty, the result will be empty as well. if (string.is_empty()) return PrimitiveString::create(vm, String {}); auto repeated = String::repeated(string, n); if (repeated.is_error()) return vm.throw_completion(ErrorType::StringRepeatCountMustNotOverflow); // 6. Return the String value that is made from n copies of S appended together. return PrimitiveString::create(vm, repeated.release_value()); } // 22.1.3.19 String.prototype.replace ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replace JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace) { auto search_value = vm.argument(0); auto replace_value = vm.argument(1); // 1. Let O be ? RequireObjectCoercible(this value). auto this_object = TRY(require_object_coercible(vm, vm.this_value())); // 2. If searchValue is neither undefined nor null, then if (!search_value.is_nullish()) { // a. Let replacer be ? GetMethod(searchValue, @@replace). auto replacer = TRY(search_value.get_method(vm, vm.well_known_symbol_replace())); // b. If replacer is not undefined, then if (replacer) { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). return TRY(call(vm, *replacer, search_value, this_object, replace_value)); } } // 3. Let string be ? ToString(O). auto string = TRY(this_object.to_utf16_string(vm)); // 4. Let searchString be ? ToString(searchValue). auto search_string = TRY(search_value.to_utf16_string(vm)); // 5. Let functionalReplace be IsCallable(replaceValue). // 6. If functionalReplace is false, then if (!replace_value.is_function()) { // a. Set replaceValue to ? ToString(replaceValue). auto replace_string = TRY(replace_value.to_utf16_string(vm)); replace_value = PrimitiveString::create(vm, move(replace_string)); } // 7. Let searchLength be the length of searchString. auto search_length = search_string.length_in_code_units(); // 8. Let position be StringIndexOf(string, searchString, 0). auto position = string_index_of(string.view(), search_string.view(), 0); // 9. If position = -1, return string. if (!position.has_value()) return PrimitiveString::create(vm, move(string)); // 10. Let preceding be the substring of string from 0 to position. auto preceding = string.substring_view(0, *position); String replacement; // 11. Let following be the substring of string from position + searchLength. auto following = string.substring_view(*position + search_length); // 12. If functionalReplace is true, then if (replace_value.is_function()) { // a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)). auto result = TRY(call(vm, replace_value.as_function(), js_undefined(), PrimitiveString::create(vm, search_string), Value(*position), PrimitiveString::create(vm, string))); replacement = TRY(result.to_string(vm)); } // 13. Else, else { // a. Assert: replaceValue is a String. VERIFY(replace_value.is_string()); // b. Let captures be a new empty List. Span captures; // c. Let replacement be ! GetSubstitution(searchString, string, position, captures, undefined, replaceValue). replacement = TRY(get_substitution(vm, search_string.view(), string.view(), *position, captures, js_undefined(), replace_value)); } // 14. Return the string-concatenation of preceding, replacement, and following. StringBuilder builder; builder.append(preceding); builder.append(replacement); builder.append(following); return PrimitiveString::create(vm, MUST(builder.to_string())); } // 22.1.3.20 String.prototype.replaceAll ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replaceall JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all) { auto search_value = vm.argument(0); auto replace_value = vm.argument(1); // 1. Let O be ? RequireObjectCoercible(this value). auto this_object = TRY(require_object_coercible(vm, vm.this_value())); // 2. If searchValue is neither undefined nor null, then if (!search_value.is_nullish()) { // a. Let isRegExp be ? IsRegExp(searchValue). bool is_regexp = TRY(search_value.is_regexp(vm)); // b. If isRegExp is true, then if (is_regexp) { // i. Let flags be ? Get(searchValue, "flags"). auto flags = TRY(search_value.as_object().get(vm.names.flags)); // ii. Perform ? RequireObjectCoercible(flags). auto flags_object = TRY(require_object_coercible(vm, flags)); // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. if (!TRY(flags_object.to_string(vm)).contains('g')) return vm.throw_completion(ErrorType::StringNonGlobalRegExp); } // c. Let replacer be ? GetMethod(searchValue, @@replace). auto replacer = TRY(search_value.get_method(vm, vm.well_known_symbol_replace())); // d. If replacer is not undefined, then if (replacer) { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). return TRY(call(vm, *replacer, search_value, this_object, replace_value)); } } // 3. Let string be ? ToString(O). auto string = TRY(this_object.to_utf16_string(vm)); // 4. Let searchString be ? ToString(searchValue). auto search_string = TRY(search_value.to_utf16_string(vm)); // 5. Let functionalReplace be IsCallable(replaceValue). // 6. If functionalReplace is false, then if (!replace_value.is_function()) { // a. Set replaceValue to ? ToString(replaceValue). auto replace_string = TRY(replace_value.to_utf16_string(vm)); replace_value = PrimitiveString::create(vm, move(replace_string)); } // 7. Let searchLength be the length of searchString. auto search_length = search_string.length_in_code_units(); // 8. Let advanceBy be max(1, searchLength). size_t advance_by = max(1u, search_length); // 9. Let matchPositions be a new empty List. Vector match_positions; // 10. Let position be StringIndexOf(string, searchString, 0). auto position = string_index_of(string.view(), search_string.view(), 0); // 11. Repeat, while position ≠ -1, while (position.has_value()) { // a. Append position to matchPositions. match_positions.append(*position); // b. Set position to StringIndexOf(string, searchString, position + advanceBy). position = string_index_of(string.view(), search_string.view(), *position + advance_by); } // 12. Let endOfLastMatch be 0. size_t end_of_last_match = 0; // 13. Let result be the empty String. StringBuilder result; // 14. For each element p of matchPositions, do for (auto position : match_positions) { // a. Let preserved be the substring of string from endOfLastMatch to p. auto preserved = string.substring_view(end_of_last_match, position - end_of_last_match); String replacement; // b. If functionalReplace is true, then if (replace_value.is_function()) { // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)). replacement = TRY(TRY(call(vm, replace_value.as_function(), js_undefined(), PrimitiveString::create(vm, search_string), Value(position), PrimitiveString::create(vm, string))).to_string(vm)); } // c. Else, else { // i. Assert: replaceValue is a String. // ii. Let captures be a new empty List. // iii. Let replacement be ! GetSubstitution(searchString, string, p, captures, undefined, replaceValue). replacement = TRY(get_substitution(vm, search_string.view(), string.view(), position, {}, js_undefined(), replace_value)); } // d. Set result to the string-concatenation of result, preserved, and replacement. result.append(preserved); result.append(replacement); // e. Set endOfLastMatch to p + searchLength. end_of_last_match = position + search_length; } auto string_length = string.length_in_code_units(); // 15. If endOfLastMatch < the length of string, then if (end_of_last_match < string_length) { // a. Set result to the string-concatenation of result and the substring of string from endOfLastMatch. result.append(string.substring_view(end_of_last_match)); } // 16. Return result. return PrimitiveString::create(vm, MUST(result.to_string())); } // 22.1.3.21 String.prototype.search ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.search JS_DEFINE_NATIVE_FUNCTION(StringPrototype::search) { auto regexp = vm.argument(0); // 1. Let O be ? RequireObjectCoercible(this value). auto this_object = TRY(require_object_coercible(vm, vm.this_value())); // 2. If regexp is neither undefined nor null, then if (!regexp.is_nullish()) { // a. Let searcher be ? GetMethod(regexp, @@search). auto searcher = TRY(regexp.get_method(vm, vm.well_known_symbol_search())); // b. If searcher is not undefined, then if (searcher) { // i. Return ? Call(searcher, regexp, « O »). return TRY(call(vm, *searcher, regexp, this_object)); } } // 3. Let string be ? ToString(O). auto string = TRY(this_object.to_utf16_string(vm)); // 4. Let rx be ? RegExpCreate(regexp, undefined). auto rx = TRY(regexp_create(vm, regexp, js_undefined())); // 5. Return ? Invoke(rx, @@search, « string »). return TRY(Value(rx).invoke(vm, vm.well_known_symbol_search(), PrimitiveString::create(vm, move(string)))); } // 22.1.3.22 String.prototype.slice ( start, end ), https://tc39.es/ecma262/#sec-string.prototype.slice JS_DEFINE_NATIVE_FUNCTION(StringPrototype::slice) { auto start = vm.argument(0); auto end = vm.argument(1); // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Let len be the length of S. auto string_length = static_cast(string.length_in_code_units()); // 4. Let intStart be ? ToIntegerOrInfinity(start). auto int_start = TRY(start.to_integer_or_infinity(vm)); // 5. If intStart = -∞, let from be 0. if (Value(int_start).is_negative_infinity()) int_start = 0; // 6. Else if intStart < 0, let from be max(len + intStart, 0). else if (int_start < 0) int_start = max(string_length + int_start, 0); // 7. Else, let from be min(intStart, len). else int_start = min(int_start, string_length); // 8. If end is undefined, let intEnd be len; else let intEnd be ? ToIntegerOrInfinity(end). auto int_end = string_length; if (!end.is_undefined()) { int_end = TRY(end.to_integer_or_infinity(vm)); // 9. If intEnd = -∞, let to be 0. if (Value(int_end).is_negative_infinity()) int_end = 0; // 10. Else if intEnd < 0, let to be max(len + intEnd, 0). else if (int_end < 0) int_end = max(string_length + int_end, 0); // 11. Else, let to be min(intEnd, len). else int_end = min(int_end, string_length); } // 12. If from ≥ to, return the empty String. if (int_start >= int_end) return PrimitiveString::create(vm, String {}); // 13. Return the substring of S from from to to. return PrimitiveString::create(vm, Utf16String::create(string.substring_view(int_start, int_end - int_start))); } // 22.1.3.23 String.prototype.split ( separator, limit ), https://tc39.es/ecma262/#sec-string.prototype.split JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) { auto& realm = *vm.current_realm(); auto separator_argument = vm.argument(0); auto limit_argument = vm.argument(1); // 1. Let O be ? RequireObjectCoercible(this value). auto object = TRY(require_object_coercible(vm, vm.this_value())); // 2. If separator is neither undefined nor null, then if (!separator_argument.is_nullish()) { // a. Let splitter be ? GetMethod(separator, @@split). auto splitter = TRY(separator_argument.get_method(vm, vm.well_known_symbol_split())); // b. If splitter is not undefined, then if (splitter) { // i. Return ? Call(splitter, separator, « O, limit »). return TRY(call(vm, *splitter, separator_argument, object, limit_argument)); } } // 3. Let S be ? ToString(O). auto string = TRY(object.to_utf16_string(vm)); // 11. Let substrings be a new empty List. auto array = MUST(Array::create(realm, 0)); size_t array_length = 0; // 4. If limit is undefined, let lim be 232 - 1; else let lim be ℝ(? ToUint32(limit)). auto limit = NumericLimits::max(); if (!limit_argument.is_undefined()) limit = TRY(limit_argument.to_u32(vm)); // 5. Let R be ? ToString(separator). auto separator = TRY(separator_argument.to_utf16_string(vm)); // 6. If lim = 0, then if (limit == 0) { // a. Return CreateArrayFromList(« »). return array; } auto string_length = string.length_in_code_units(); // 7. If separator is undefined, then if (separator_argument.is_undefined()) { // a. Return CreateArrayFromList(« S »). MUST(array->create_data_property_or_throw(0, PrimitiveString::create(vm, move(string)))); return array; } // 8. Let separatorLength be the length of R. auto separator_length = separator.length_in_code_units(); // 10. If S is the empty String, return CreateArrayFromList(« S »). if (string_length == 0) { if (separator_length > 0) MUST(array->create_data_property_or_throw(0, PrimitiveString::create(vm, move(string)))); return array; } // 12. Let i be 0. size_t start = 0; // 13. Let j be StringIndexOf(S, R, 0). auto position = start; // 14. Repeat, while j ≠ -1, while (position != string_length) { // a. Let T be the substring of S from i to j. auto match = split_match(string.view(), position, separator.view()); if (!match.has_value() || match.value() == start) { ++position; continue; } auto segment = string.substring_view(start, position - start); // b. Append T to substrings. MUST(array->create_data_property_or_throw(array_length, PrimitiveString::create(vm, Utf16String::create(segment)))); ++array_length; // c. If the number of elements in substrings is lim, return CreateArrayFromList(substrings). if (array_length == limit) return array; // d. Set i to j + separatorLength. start = match.value(); // e. Set j to StringIndexOf(S, R, i). position = start; } // 15. Let T be the substring of S from i. auto rest = string.substring_view(start); // 16. Append T to substrings. MUST(array->create_data_property_or_throw(array_length, PrimitiveString::create(vm, Utf16String::create(rest)))); // 17. Return CreateArrayFromList(substrings). return array; } // 22.1.3.24 String.prototype.startsWith ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.startswith JS_DEFINE_NATIVE_FUNCTION(StringPrototype::starts_with) { auto search_string_value = vm.argument(0); auto position = vm.argument(1); // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Let isRegExp be ? IsRegExp(searchString). bool is_regexp = TRY(search_string_value.is_regexp(vm)); // 4. If isRegExp is true, throw a TypeError exception. if (is_regexp) return vm.throw_completion(ErrorType::IsNotA, "searchString", "string, but a regular expression"); // 5. Let searchStr be ? ToString(searchString). auto search_string = TRY(search_string_value.to_utf16_string(vm)); // 6. Let len be the length of S. auto string_length = string.length_in_code_units(); size_t start = 0; // 7. If position is undefined, let pos be 0; else let pos be ? ToIntegerOrInfinity(position). if (!position.is_undefined()) { auto pos = TRY(position.to_integer_or_infinity(vm)); // 8. Let start be the result of clamping pos between 0 and len. start = clamp(pos, static_cast(0), static_cast(string_length)); } // 9. Let searchLength be the length of searchStr. auto search_length = search_string.length_in_code_units(); // 10. If searchLength = 0, return true. if (search_length == 0) return Value(true); // 11. Let end be start + searchLength. size_t end = start + search_length; // 12. If end > len, return false. if (end > string_length) return Value(false); // 13. Let substring be the substring of S from start to end. auto substring_view = string.substring_view(start, end - start); // 14. If substring is searchStr, return true. // 15. Return false. return Value(substring_view == search_string.view()); } // 22.1.3.25 String.prototype.substring ( start, end ), https://tc39.es/ecma262/#sec-string.prototype.substring JS_DEFINE_NATIVE_FUNCTION(StringPrototype::substring) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // 3. Let len be the length of S. auto string_length = static_cast(string.length_in_code_units()); // 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)); // 6. Let finalStart be the result of clamping intStart between 0 and len. size_t final_start = clamp(start, static_cast(0), string_length); // 7. Let finalEnd be the result of clamping intEnd between 0 and len. size_t final_end = clamp(end, static_cast(0), string_length); // 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); // 10. Return the substring of S from from to to. return PrimitiveString::create(vm, Utf16String::create(string.substring_view(from, to - from))); } enum class TargetCase { Lower, Upper, }; // 19.1.2.1 TransformCase ( S, locales, targetCase ), https://tc39.es/ecma402/#sec-transform-case static ThrowCompletionOr transform_case(VM& vm, String const& string, Value locales, TargetCase target_case) { // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). auto requested_locales = TRY(Intl::canonicalize_locale_list(vm, locales)); String requested_locale; // 2. If requestedLocales is not an empty List, then if (!requested_locales.is_empty()) { // a. Let requestedLocale be requestedLocales[0]. requested_locale = requested_locales[0]; } // 3. Else, else { // a. Let requestedLocale be ! DefaultLocale(). requested_locale = String::from_utf8_without_validation(Unicode::default_locale().bytes()); } // 4. Let availableLocales be an Available Locales List which includes the language tags for which the Unicode Character Database contains language-sensitive case mappings. If the implementation supports additional locale-sensitive case mappings, availableLocales should also include their corresponding language tags. // 5. Let match be LookupMatchingLocaleByPrefix(availableLocales, « requestedLocale »). auto match = Intl::lookup_matching_locale_by_prefix({ { requested_locale } }); // 6. If match is not undefined, let locale be match.[[locale]]; else let locale be "und". StringView locale = match.has_value() ? match->locale : "und"sv; // 7. Let codePoints be StringToCodePoints(S). String new_code_points; switch (target_case) { // 8. 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 = MUST(string.to_lowercase(locale)); break; // 9. 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 = MUST(string.to_uppercase(locale)); break; default: VERIFY_NOT_REACHED(); } // 10. Return CodePointsToString(newCodePoints). return new_code_points; } // 22.1.3.26 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 locales = vm.argument(0); // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf8_string_from(vm)); // 3. Return ? TransformCase(S, locales, lower). return PrimitiveString::create(vm, TRY(transform_case(vm, string, locales, TargetCase::Lower))); } // 22.1.3.27 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); // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf8_string_from(vm)); // 3. Return ? TransformCase(S, locales, upper). return PrimitiveString::create(vm, TRY(transform_case(vm, string, locales, TargetCase::Upper))); } // 22.1.3.28 String.prototype.toLowerCase ( ), https://tc39.es/ecma262/#sec-string.prototype.tolowercase JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_lowercase) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). // 3. Let sText be StringToCodePoints(S). auto string = TRY(utf8_string_from(vm)); // 4. Let lowerText be the result of toLowercase(sText), according to the Unicode Default Case Conversion algorithm. auto lowercase = MUST(string.to_lowercase()); // 5. Let L be CodePointsToString(lowerText). // 6. Return L. return PrimitiveString::create(vm, move(lowercase)); } // 22.1.3.29 String.prototype.toString ( ), https://tc39.es/ecma262/#sec-string.prototype.tostring JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_string) { // 1. Return ? thisStringValue(this value). return TRY(this_string_value(vm, vm.this_value())); } // 22.1.3.30 String.prototype.toUpperCase ( ), https://tc39.es/ecma262/#sec-string.prototype.touppercase JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_uppercase) { // This method interprets a String value as a sequence of UTF-16 encoded code points, as described in 6.1.4. // It behaves in exactly the same way as String.prototype.toLowerCase, except that the String is mapped using the toUppercase algorithm of the Unicode Default Case Conversion. auto string = TRY(utf8_string_from(vm)); auto uppercase = MUST(string.to_uppercase()); return PrimitiveString::create(vm, move(uppercase)); } // 22.1.3.31 String.prototype.toWellFormed ( ), https://tc39.es/ecma262/#sec-string.prototype.towellformed JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_well_formed) { // 1. Let O be ? RequireObjectCoercible(this value). // 2. Let S be ? ToString(O). auto string = TRY(utf16_string_from(vm)); // NOTE: Rest of steps in to_well_formed below return PrimitiveString::create(vm, to_well_formed_string(string)); } // https://tc39.es/ecma262/#sec-string.prototype.towellformed String to_well_formed_string(Utf16String const& string) { // 3. Let strLen be the length of S. auto length = string.length_in_code_units(); // 4. Let k be 0. size_t k = 0; // 5. Let result be the empty String. StringBuilder result; // 6. Repeat, while k < strLen, while (k < length) { // a. Let cp be CodePointAt(S, k). auto code_point = JS::code_point_at(string.view(), k); // b. If cp.[[IsUnpairedSurrogate]] is true, then if (code_point.is_unpaired_surrogate) { // i. Set result to the string-concatenation of result and 0xFFFD (REPLACEMENT CHARACTER). result.append_code_point(0xfffd); } // c. Else, else { // i. Set result to the string-concatenation of result and UTF16EncodeCodePoint(cp.[[CodePoint]]). result.append_code_point(code_point.code_point); } // d. Set k to k + cp.[[CodeUnitCount]]. k += code_point.code_unit_count; } // 7. Return result. return MUST(result.to_string()); } // 22.1.3.32.1 TrimString ( string, where ), https://tc39.es/ecma262/#sec-trimstring ThrowCompletionOr trim_string(VM& vm, Value input_value, TrimMode where) { // 1. Let str be ? RequireObjectCoercible(string). auto input_string = TRY(require_object_coercible(vm, input_value)); // 2. Let S be ? ToString(str). auto string = TRY(input_string.to_string(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(); // 6. Return T. return MUST(String::from_utf8(trimmed_string)); } // 22.1.3.32 String.prototype.trim ( ), https://tc39.es/ecma262/#sec-string.prototype.trim JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim) { // 1. Let S be the this value. // 2. Return ? TrimString(S, start+end). return PrimitiveString::create(vm, TRY(trim_string(vm, vm.this_value(), TrimMode::Both))); } // 22.1.3.33 String.prototype.trimEnd ( ), https://tc39.es/ecma262/#sec-string.prototype.trimend JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_end) { // 1. Let S be the this value. // 2. Return ? TrimString(S, end). return PrimitiveString::create(vm, TRY(trim_string(vm, vm.this_value(), TrimMode::Right))); } // 22.1.3.34 String.prototype.trimStart ( ), https://tc39.es/ecma262/#sec-string.prototype.trimstart JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_start) { // 1. Let S be the this value. // 2. Return ? TrimString(S, start). return PrimitiveString::create(vm, TRY(trim_string(vm, vm.this_value(), TrimMode::Left))); } // 22.1.3.35 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())); } // 22.1.3.36 String.prototype [ @@iterator ] ( ), https://tc39.es/ecma262/#sec-string.prototype-@@iterator JS_DEFINE_NATIVE_FUNCTION(StringPrototype::symbol_iterator) { auto& realm = *vm.current_realm(); // 1. Let O be ? RequireObjectCoercible(this value). auto this_object = TRY(require_object_coercible(vm, vm.this_value())); // 2. Let s be ? ToString(O). auto string = TRY(this_object.to_string(vm)); // 3. Let closure be a new Abstract Closure with no parameters that captures s and performs the following steps when called: // ... // 4. Return CreateIteratorFromClosure(closure, "%StringIteratorPrototype%", %StringIteratorPrototype%). return StringIterator::create(realm, 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)); // 3. Let size be the length of S. auto size = string.length_in_code_units(); // 4. Let intStart be ? ToIntegerOrInfinity(start). auto int_start = TRY(vm.argument(0).to_integer_or_infinity(vm)); // 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); // 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)); // 9. Set intLength to the result of clamping intLength between 0 and size. int_length = clamp(int_length, 0, size); // 10. Let intEnd be min(intStart + intLength, size). auto int_end = min((i32)(int_start + int_length), size); if (int_start >= int_end) return PrimitiveString::create(vm, String {}); // 11. Return the substring of S from intStart to intEnd. return PrimitiveString::create(vm, Utf16String::create(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 static ThrowCompletionOr create_html(VM& vm, Value string, StringView tag, StringView attribute, Value value) { // 1. Let str be ? RequireObjectCoercible(string). TRY(require_object_coercible(vm, string)); // 2. Let S be ? ToString(str). auto str = TRY(string.to_string(vm)); // 3. Let p1 be the string-concatenation of "<" and tag. StringBuilder builder; builder.append('<'); builder.append(tag); // 4. If attribute is not the empty String, then if (!attribute.is_empty()) { // a. Let V be ? ToString(value). auto value_string = TRY(value.to_string(vm)); // b. Let escapedV be the String value that is the same as V except that each occurrence of the code unit 0x0022 (QUOTATION MARK) in V has been replaced with the six code unit sequence """. auto escaped_value_string = MUST(value_string.replace("\""sv, """sv, ReplaceMode::All)); // c. Set p1 to the string-concatenation of: // - p1 // - the code unit 0x0020 (SPACE) builder.append(' '); // - attribute builder.append(attribute); // - the code unit 0x003D (EQUALS SIGN) // - the code unit 0x0022 (QUOTATION MARK) builder.append("=\""sv); // - escapedV builder.append(escaped_value_string); // - the code unit 0x0022 (QUOTATION MARK) builder.append('"'); } // 5. Let p2 be the string-concatenation of p1 and ">". builder.append('>'); // 6. Let p3 be the string-concatenation of p2 and S. builder.append(str); // 7. Let p4 be the string-concatenation of p3, "". builder.append("'); // 8. Return p4. return PrimitiveString::create(vm, MUST(builder.to_string())); } // B.2.2.2 String.prototype.anchor ( name ), https://tc39.es/ecma262/#sec-string.prototype.anchor JS_DEFINE_NATIVE_FUNCTION(StringPrototype::anchor) { auto name = vm.argument(0); // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "a", "name", name). return create_html(vm, vm.this_value(), "a"sv, "name"sv, name); } // B.2.2.3 String.prototype.big ( ), https://tc39.es/ecma262/#sec-string.prototype.big JS_DEFINE_NATIVE_FUNCTION(StringPrototype::big) { // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "big", "", ""). return create_html(vm, vm.this_value(), "big"sv, {}, Value()); } // B.2.2.4 String.prototype.blink ( ), https://tc39.es/ecma262/#sec-string.prototype.blink JS_DEFINE_NATIVE_FUNCTION(StringPrototype::blink) { // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "blink", "", ""). return create_html(vm, vm.this_value(), "blink"sv, {}, Value()); } // B.2.2.5 String.prototype.bold ( ), https://tc39.es/ecma262/#sec-string.prototype.bold JS_DEFINE_NATIVE_FUNCTION(StringPrototype::bold) { // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "b", "", ""). return create_html(vm, vm.this_value(), "b"sv, {}, Value()); } // B.2.2.6 String.prototype.fixed ( ), https://tc39.es/ecma262/#sec-string.prototype.fixed JS_DEFINE_NATIVE_FUNCTION(StringPrototype::fixed) { // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "tt", "", ""). return create_html(vm, vm.this_value(), "tt"sv, {}, Value()); } // B.2.2.7 String.prototype.fontcolor ( color ), https://tc39.es/ecma262/#sec-string.prototype.fontcolor JS_DEFINE_NATIVE_FUNCTION(StringPrototype::fontcolor) { auto color = vm.argument(0); // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "font", "color", color). return create_html(vm, vm.this_value(), "font"sv, "color"sv, color); } // B.2.2.8 String.prototype.fontsize ( size ), https://tc39.es/ecma262/#sec-string.prototype.fontsize JS_DEFINE_NATIVE_FUNCTION(StringPrototype::fontsize) { auto size = vm.argument(0); // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "font", "size", size). return create_html(vm, vm.this_value(), "font"sv, "size"sv, size); } // B.2.2.9 String.prototype.italics ( ), https://tc39.es/ecma262/#sec-string.prototype.italics JS_DEFINE_NATIVE_FUNCTION(StringPrototype::italics) { // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "i", "", ""). return create_html(vm, vm.this_value(), "i"sv, {}, Value()); } // B.2.2.10 String.prototype.link ( url ), https://tc39.es/ecma262/#sec-string.prototype.link JS_DEFINE_NATIVE_FUNCTION(StringPrototype::link) { auto url = vm.argument(0); // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "a", "href", url). return create_html(vm, vm.this_value(), "a"sv, "href"sv, url); } // B.2.2.11 String.prototype.small ( ), https://tc39.es/ecma262/#sec-string.prototype.small JS_DEFINE_NATIVE_FUNCTION(StringPrototype::small) { // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "small", "", ""). return create_html(vm, vm.this_value(), "small"sv, {}, Value()); } // B.2.2.12 String.prototype.strike ( ), https://tc39.es/ecma262/#sec-string.prototype.strike JS_DEFINE_NATIVE_FUNCTION(StringPrototype::strike) { // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "strike", "", ""). return create_html(vm, vm.this_value(), "strike"sv, {}, Value()); } // B.2.2.13 String.prototype.sub ( ), https://tc39.es/ecma262/#sec-string.prototype.sub JS_DEFINE_NATIVE_FUNCTION(StringPrototype::sub) { // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "sub", "", ""). return create_html(vm, vm.this_value(), "sub"sv, {}, Value()); } // B.2.2.14 String.prototype.sup ( ), https://tc39.es/ecma262/#sec-string.prototype.sup JS_DEFINE_NATIVE_FUNCTION(StringPrototype::sup) { // 1. Let S be the this value. // 2. Return ? CreateHTML(S, "sup", "", ""). return create_html(vm, vm.this_value(), "sup"sv, {}, Value()); } }