Bug 1260509 - Implement String.prototype.padStart / padEnd; r=jorendorff
authorMorgan Phillips <winter2718@gmail.com>
Thu, 31 Mar 2016 08:04:12 -0700
changeset 291231 d3ba5b5019c6cd9348787e42ad0596c4d535cd14
parent 291230 329a66b8e67c1c9367fbd496752e0488723aa79b
child 291232 b6ea6a3bb8a6fc355b46403919d8c70e798c7007
push id74526
push usermphillips@mozilla.com
push dateFri, 01 Apr 2016 17:52:27 +0000
treeherdermozilla-inbound@d3ba5b5019c6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1260509
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1260509 - Implement String.prototype.padStart / padEnd; r=jorendorff
js/src/builtin/String.js
js/src/jsstr.cpp
js/src/tests/ecma_7/String/shell.js
js/src/tests/ecma_7/String/string-pad-start-end.js
--- a/js/src/builtin/String.js
+++ b/js/src/builtin/String.js
@@ -1,14 +1,63 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*global intl_Collator: false, */
 
+/**
+ * A helper function implementing the logic for both String.prototype.padStart
+ * and String.prototype.padEnd as described in ES7 Draft March 29, 2016
+ */
+function String_pad(maxLength, fillString, padEnd=false) {
+
+    // Steps 1-2.
+    RequireObjectCoercible(this);
+    let str = ToString(this);
+
+    // Steps 3-4.
+    let intMaxLength = ToLength(maxLength);
+    let strLen = str.length;
+
+    // Step 5.
+    if (intMaxLength <= strLen)
+        return str;
+
+    // Steps 6-7.
+    let filler = fillString === undefined ? " " : ToString(fillString);
+
+    // Step 8.
+    if (filler === "")
+        return str;
+
+    // Step 9.
+    let fillLen = intMaxLength - strLen;
+
+    // Step 10.
+    let truncatedStringFiller = callFunction(String_repeat, filler,
+                                             fillLen / filler.length);
+
+    truncatedStringFiller += callFunction(String_substr, filler, 0,
+                                          fillLen % filler.length);
+
+    // Step 11.
+    if (padEnd === true)
+        return str + truncatedStringFiller;
+    return truncatedStringFiller + str;
+}
+
+function String_pad_start(maxLength, fillString=" ") {
+    return callFunction(String_pad, this, maxLength, fillString, false);
+}
+
+function String_pad_end(maxLength, fillString=" ") {
+    return callFunction(String_pad, this, maxLength, fillString, true);
+}
+
 /* ES6 Draft Oct 14, 2014 21.1.3.19 */
 function String_substring(start, end) {
     // Steps 1-3.
     RequireObjectCoercible(this);
     var str = ToString(this);
 
     // Step 4.
     var len = str.length;
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -4183,16 +4183,18 @@ static const JSFunctionSpec string_metho
     /* Java-like methods. */
     JS_FN(js_toString_str,     str_toString,          0,0),
     JS_FN(js_valueOf_str,      str_toString,          0,0),
     JS_FN("toLowerCase",       str_toLowerCase,       0,JSFUN_GENERIC_NATIVE),
     JS_FN("toUpperCase",       str_toUpperCase,       0,JSFUN_GENERIC_NATIVE),
     JS_INLINABLE_FN("charAt",  str_charAt,            1,JSFUN_GENERIC_NATIVE, StringCharAt),
     JS_INLINABLE_FN("charCodeAt", str_charCodeAt,     1,JSFUN_GENERIC_NATIVE, StringCharCodeAt),
     JS_SELF_HOSTED_FN("substring", "String_substring", 2,0),
+    JS_SELF_HOSTED_FN("padStart", "String_pad_start", 2,0),
+    JS_SELF_HOSTED_FN("padEnd", "String_pad_end", 2,0),
     JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0),
     JS_FN("includes",          str_includes,          1,JSFUN_GENERIC_NATIVE),
     JS_FN("contains",          str_contains,          1,JSFUN_GENERIC_NATIVE),
     JS_FN("indexOf",           str_indexOf,           1,JSFUN_GENERIC_NATIVE),
     JS_FN("lastIndexOf",       str_lastIndexOf,       1,JSFUN_GENERIC_NATIVE),
     JS_FN("startsWith",        str_startsWith,        1,JSFUN_GENERIC_NATIVE),
     JS_FN("endsWith",          str_endsWith,          1,JSFUN_GENERIC_NATIVE),
     JS_FN("trim",              str_trim,              0,JSFUN_GENERIC_NATIVE),
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/String/string-pad-start-end.js
@@ -0,0 +1,99 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+// `this` must be object coercable.
+
+for (let badThis of [null, undefined]) {
+    assertThrowsInstanceOf(() => {
+        String.prototype.padStart.call(badThis, 42, "oups");
+    }, TypeError);
+
+    assertThrowsInstanceOf(() => {
+        String.prototype.padEnd.call(badThis, 42, "oups");
+    }, TypeError);
+}
+
+let proxy = new Proxy({}, {
+get(t, name) {
+  if (name === Symbol.toPrimitive || name === "toString") return;
+  if (name === "valueOf") return () => 42;
+  throw "This should not be reachable";
+}
+});
+
+assertEq("42bloop", String.prototype.padEnd.call(proxy, 7, "bloopie"));
+
+// maxLength must convert to an integer
+
+assertEq("lame", "lame".padStart(0, "foo"));
+assertEq("lame", "lame".padStart(0.1119, "foo"));
+assertEq("lame", "lame".padStart(-0, "foo"));
+assertEq("lame", "lame".padStart(NaN, "foo"));
+assertEq("lame", "lame".padStart(-1, "foo"));
+assertEq("lame", "lame".padStart({toString: () => 0}, "foo"));
+
+assertEq("lame", "lame".padEnd(0, "foo"));
+assertEq("lame", "lame".padEnd(0.1119, "foo"));
+assertEq("lame", "lame".padEnd(-0, "foo"));
+assertEq("lame", "lame".padEnd(NaN, "foo"));
+assertEq("lame", "lame".padEnd(-1, "foo"));
+assertEq("lame", "lame".padEnd({toString: () => 0}, "foo"));
+
+assertThrowsInstanceOf(() => {
+    "lame".padStart(Symbol("9900"), 0);
+}, TypeError);
+
+assertThrowsInstanceOf(() => {
+    "lame".padEnd(Symbol("9900"), 0);
+}, TypeError);
+
+// The fill argument must be string coercable.
+
+assertEq("nulln.", ".".padStart(6, null));
+assertEq(".nulln", ".".padEnd(6, null));
+
+assertEq("[obje.", ".".padStart(6, {}));
+assertEq(".[obje", ".".padEnd(6, {}));
+
+assertEq("1,2,3.", ".".padStart(6, [1, 2, 3]));
+assertEq(".1,2,3", ".".padEnd(6, [1, 2, 3]));
+
+assertEq("aaaaa.", ".".padStart(6, {toString: () => "a"}));
+assertEq(".aaaaa", ".".padEnd(6, {toString: () => "a"}));
+
+// undefined is converted to " "
+
+assertEq("     .", ".".padStart(6, undefined));
+assertEq(".     ", ".".padEnd(6, undefined));
+
+assertEq("     .", ".".padStart(6));
+assertEq(".     ", ".".padEnd(6));
+
+// The empty string has no effect
+
+assertEq("Tilda", "Tilda".padStart(100000, ""));
+assertEq("Tilda", "Tilda".padEnd(100000, ""));
+
+assertEq("Tilda", "Tilda".padStart(100000, {toString: () => ""}));
+assertEq("Tilda", "Tilda".padEnd(100000, {toString: () => ""}));
+
+// Test repetition against a bruteforce implementation
+
+let filler = "space";
+let truncatedFiller = "";
+for (let i = 0; i < 2500; i++) {
+    truncatedFiller += filler[i % filler.length];
+    assertEq(truncatedFiller + "goto", "goto".padStart(5 + i, filler));
+    assertEq("goto" + truncatedFiller, "goto".padEnd(5 + i, filler));
+}
+
+// [Argument] Length
+
+assertEq(1, String.prototype.padStart.length)
+assertEq(1, String.prototype.padEnd.length)
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
+