diff --git a/Libraries/LibJS/Runtime/StringPrototype.cpp b/Libraries/LibJS/Runtime/StringPrototype.cpp index 5d8f97a909..6b9fa42c3a 100644 --- a/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -51,6 +51,10 @@ StringPrototype::StringPrototype() put_native_function("toString", to_string, 0); put_native_function("padStart", pad_start, 1); put_native_function("padEnd", pad_end, 1); + + put_native_function("trim", trim, 0); + put_native_function("trimStart", trimStart, 0); + put_native_function("trimEnd", trimEnd, 0); } StringPrototype::~StringPrototype() @@ -235,4 +239,70 @@ Value StringPrototype::pad_end(Interpreter& interpreter) return pad_string(interpreter, this_object, PadPlacement::End); } +enum class TrimMode { + Left, + Right, + Both +}; + +static Value trim_string(Interpreter& interpreter, Object* object, TrimMode mode) +{ + auto& string = object->to_string().as_string()->string(); + + size_t substring_start = 0; + size_t substring_length = string.length(); + + auto is_white_space_character = [](char character) -> bool { + return character == 0x9 || character == 0xa || character == 0xb || character == 0xc || character == 0xd || character == 0x20; + }; + + if (mode == TrimMode::Left || mode == TrimMode::Both) { + for (size_t i = 0; i < string.length(); ++i) { + if (!is_white_space_character(string.characters()[i])) { + substring_start = i; + substring_length -= substring_start; + break; + } + } + } + + if (substring_length == 0) + return js_string(interpreter, String()); + + if (mode == TrimMode::Right || mode == TrimMode::Both) { + size_t count = 0; + for (size_t i = string.length() - 1; i > 0; --i) { + if (!is_white_space_character(string.characters()[i])) { + substring_length -= count; + break; + } + count++; + } + } + + auto substring = string.substring(substring_start, substring_length); + return js_string(interpreter, substring); +} + +Value StringPrototype::trim(Interpreter& interpreter) +{ + auto* this_object = interpreter.this_value().to_object(interpreter.heap()); + + return trim_string(interpreter, this_object, TrimMode::Both); +} + +Value StringPrototype::trimStart(Interpreter& interpreter) +{ + auto* this_object = interpreter.this_value().to_object(interpreter.heap()); + + return trim_string(interpreter, this_object, TrimMode::Left); +} + +Value StringPrototype::trimEnd(Interpreter& interpreter) +{ + auto* this_object = interpreter.this_value().to_object(interpreter.heap()); + + return trim_string(interpreter, this_object, TrimMode::Right); +} + } diff --git a/Libraries/LibJS/Runtime/StringPrototype.h b/Libraries/LibJS/Runtime/StringPrototype.h index b5ee2649e6..ac7fb13a92 100644 --- a/Libraries/LibJS/Runtime/StringPrototype.h +++ b/Libraries/LibJS/Runtime/StringPrototype.h @@ -49,6 +49,10 @@ private: static Value pad_end(Interpreter&); static Value length_getter(Interpreter&); + + static Value trim(Interpreter&); + static Value trimStart(Interpreter&); + static Value trimEnd(Interpreter&); }; } diff --git a/Libraries/LibJS/Tests/String.prototype.trim.js b/Libraries/LibJS/Tests/String.prototype.trim.js new file mode 100644 index 0000000000..d8eb446767 --- /dev/null +++ b/Libraries/LibJS/Tests/String.prototype.trim.js @@ -0,0 +1,56 @@ +load("test-common.js"); + +try { + assert(String.prototype.trim.length === 0); + assert(String.prototype.trimStart.length === 0); + assert(String.prototype.trimEnd.length === 0); + assert(" hello friends ".trim() === "hello friends"); + assert("hello friends ".trim() === "hello friends"); + assert(" hello friends".trim() === "hello friends"); + assert(" hello friends".trimStart() === "hello friends"); + assert("hello friends ".trimEnd() === "hello friends"); + assert(" hello friends".trimEnd() === " hello friends"); + assert("hello friends ".trimStart() === "hello friends "); + assert(" hello friends ".trimEnd() === " hello friends"); + assert(" hello friends ".trimStart() === "hello friends "); + + assert("\thello friends".trimStart() === "hello friends"); + assert("hello friends\t".trimStart() === "hello friends\t"); + assert("\thello friends\t".trimStart() === "hello friends\t"); + + assert("\rhello friends".trimStart() === "hello friends"); + assert("hello friends\r".trimStart() === "hello friends\r"); + assert("\rhello friends\r".trimStart() === "hello friends\r"); + + assert("hello friends\t".trimEnd() === "hello friends"); + assert("\thello friends".trimEnd() === "\thello friends"); + assert("\thello friends\t".trimEnd() === "\thello friends"); + + assert("hello friends\r".trimEnd() === "hello friends"); + assert("\rhello friends".trimEnd() === "\rhello friends"); + assert("\rhello friends\r".trimEnd() === "\rhello friends"); + + assert("hello friends\n".trimEnd() === "hello friends"); + assert("\r\nhello friends".trimEnd() === "\r\nhello friends"); + assert("\rhello friends\r\n".trimEnd() === "\rhello friends"); + + assert("\thello friends\t".trim() === "hello friends"); + assert("\thello friends".trim() === "hello friends"); + assert("hello friends\t".trim() === "hello friends"); + + assert("\rhello friends\r".trim() === "hello friends"); + assert("\rhello friends".trim() === "hello friends"); + assert("hello friends\r".trim() === "hello friends"); + + assert("\rhello friends\n".trim() === "hello friends"); + assert("\r\thello friends".trim() === "hello friends"); + assert("hello friends\r\n".trim() === "hello friends"); + assert(" \thello friends\r\n".trim() === "hello friends"); + assert("\n\t\thello friends\r\n".trim() === "hello friends"); + assert("\n\t\thello friends\t\t".trim() === "hello friends"); + + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +}