From ce6a8a6e4162c7a3b24b9864c2ab6254370d1592 Mon Sep 17 00:00:00 2001 From: teppeis Date: Fri, 25 Jan 2019 09:24:07 +0900 Subject: [PATCH] Fix ES6 polyfills to use ToInteger and ToLength --- .../jscomp/js/es6/array/copywithin.js | 24 ++++---------- .../javascript/jscomp/js/es6/array/fill.js | 21 ++++++++---- .../jscomp/js/es6/array/includes.js | 10 ++++-- .../jscomp/js/es6/string/codepointat.js | 6 ++-- .../jscomp/js/es6/string/endswith.js | 5 +-- .../javascript/jscomp/js/es6/string/padend.js | 8 +++-- .../jscomp/js/es6/string/padstart.js | 4 ++- .../javascript/jscomp/js/es6/string/repeat.js | 3 +- .../jscomp/js/es6/string/startswith.js | 3 +- .../javascript/jscomp/js/util/tointeger.js | 31 +++++++++++++++++ .../javascript/jscomp/js/util/tolength.js | 33 +++++++++++++++++++ .../polyfill_tests/array_copywithin_test.js | 13 ++++++++ .../polyfill_tests/array_fill_test.js | 12 +++++++ .../polyfill_tests/array_includes_test.js | 31 +++++++++++++++++ .../polyfill_tests/string_codepointat_test.js | 1 + .../polyfill_tests/string_endswith_test.js | 3 ++ .../string_fromcodepoint_test.js | 1 + .../polyfill_tests/string_repeat_test.js | 1 + .../polyfill_tests/string_startswith_test.js | 2 ++ 19 files changed, 173 insertions(+), 39 deletions(-) create mode 100644 src/com/google/javascript/jscomp/js/util/tointeger.js create mode 100644 src/com/google/javascript/jscomp/js/util/tolength.js diff --git a/src/com/google/javascript/jscomp/js/es6/array/copywithin.js b/src/com/google/javascript/jscomp/js/es6/array/copywithin.js index 13b05dbed35..1b5b409324f 100644 --- a/src/com/google/javascript/jscomp/js/es6/array/copywithin.js +++ b/src/com/google/javascript/jscomp/js/es6/array/copywithin.js @@ -15,11 +15,9 @@ */ 'require util/polyfill'; +'require util/tointeger'; $jscomp.polyfill('Array.prototype.copyWithin', function(orig) { - // requires strict mode to throw for invalid `this` or params - 'use strict'; - if (orig) return orig; /** @@ -33,10 +31,12 @@ $jscomp.polyfill('Array.prototype.copyWithin', function(orig) { * @template VALUE */ var polyfill = function(target, start, opt_end) { + // TODO(tjgq): requires strict mode, lost in transpilation (b/24413211) + 'use strict'; var len = this.length; - target = toInteger(target); - start = toInteger(start); - var end = opt_end === undefined ? len : toInteger(opt_end); + target = $jscomp.toInteger(target); + start = $jscomp.toInteger(start); + var end = opt_end === undefined ? len : $jscomp.toInteger(opt_end); var to = target < 0 ? Math.max(len + target, 0) : Math.min(target, len); var from = start < 0 ? Math.max(len + start, 0) : Math.min(start, len); var final = end < 0 ? Math.max(len + end, 0) : Math.min(end, len); @@ -63,17 +63,5 @@ $jscomp.polyfill('Array.prototype.copyWithin', function(orig) { return this; }; - /** - * @param {number} arg - * @return {number} - */ - function toInteger(arg) { - var n = Number(arg); - if (n === Infinity || n === -Infinity) { - return n; - } - return n | 0; - } - return polyfill; }, 'es6', 'es3'); diff --git a/src/com/google/javascript/jscomp/js/es6/array/fill.js b/src/com/google/javascript/jscomp/js/es6/array/fill.js index b8425da46fd..61743c0025d 100644 --- a/src/com/google/javascript/jscomp/js/es6/array/fill.js +++ b/src/com/google/javascript/jscomp/js/es6/array/fill.js @@ -15,6 +15,8 @@ */ 'require util/polyfill'; +'require util/tointeger'; +'require util/tolength'; $jscomp.polyfill('Array.prototype.fill', function(orig) { if (orig) return orig; @@ -31,14 +33,19 @@ $jscomp.polyfill('Array.prototype.fill', function(orig) { * @suppress {reportUnknownTypes, strictPrimitiveOperators} */ var polyfill = function(value, opt_start, opt_end) { - var length = this.length || 0; - if (opt_start < 0) { - opt_start = Math.max(0, length + /** @type {number} */ (opt_start)); + 'use strict'; + var length = $jscomp.toLength(this.length); + var start = $jscomp.toInteger(opt_start); + if (start < 0) { + start = Math.max(0, length + start); } - if (opt_end == null || opt_end > length) opt_end = length; - opt_end = Number(opt_end); - if (opt_end < 0) opt_end = Math.max(0, length + opt_end); - for (var i = Number(opt_start || 0); i < opt_end; i++) { + var end = opt_end === undefined ? length : $jscomp.toInteger(opt_end); + if (end < 0) { + end = Math.max(0, length + end); + } else if (end > length) { + end = length; + } + for (var i = start; i < end; i++) { this[i] = value; } return this; diff --git a/src/com/google/javascript/jscomp/js/es6/array/includes.js b/src/com/google/javascript/jscomp/js/es6/array/includes.js index 192364021e6..4112ad9c549 100644 --- a/src/com/google/javascript/jscomp/js/es6/array/includes.js +++ b/src/com/google/javascript/jscomp/js/es6/array/includes.js @@ -16,6 +16,8 @@ 'require es6/object/is'; 'require util/polyfill'; +'require util/tointeger'; +'require util/tolength'; $jscomp.polyfill('Array.prototype.includes', function(orig) { if (orig) return orig; @@ -33,12 +35,16 @@ $jscomp.polyfill('Array.prototype.includes', function(orig) { * @suppress {reportUnknownTypes} */ var includes = function(searchElement, opt_fromIndex) { + 'use strict'; var array = this; if (array instanceof String) { array = /** @type {!IArrayLike} */ (String(array)); } - var len = array.length; - var i = opt_fromIndex || 0; + var len = $jscomp.toLength(array.length); + if (len === 0) { + return false; + } + var i = $jscomp.toInteger(opt_fromIndex); if (i < 0) { i = Math.max(i + len, 0); } diff --git a/src/com/google/javascript/jscomp/js/es6/string/codepointat.js b/src/com/google/javascript/jscomp/js/es6/string/codepointat.js index 6f3c756f284..78c5f5ed095 100644 --- a/src/com/google/javascript/jscomp/js/es6/string/codepointat.js +++ b/src/com/google/javascript/jscomp/js/es6/string/codepointat.js @@ -16,6 +16,7 @@ 'require util/checkstringargs'; 'require util/polyfill'; +'require util/tointeger'; $jscomp.polyfill('String.prototype.codePointAt', function(orig) { if (orig) return orig; @@ -34,13 +35,10 @@ $jscomp.polyfill('String.prototype.codePointAt', function(orig) { 'use strict'; var string = $jscomp.checkStringArgs(this, null, 'codePointAt'); var size = string.length; - // Make 'position' a number (non-number coerced to NaN and then or to zero). - position = Number(position) || 0; + position = $jscomp.toInteger(position); if (!(position >= 0 && position < size)) { return void 0; } - // Truncate 'position' to an integer. - position = position | 0; var first = string.charCodeAt(position); if (first < 0xD800 || first > 0xDBFF || position + 1 === size) { return first; diff --git a/src/com/google/javascript/jscomp/js/es6/string/endswith.js b/src/com/google/javascript/jscomp/js/es6/string/endswith.js index fa9ec433b13..347f91b7887 100644 --- a/src/com/google/javascript/jscomp/js/es6/string/endswith.js +++ b/src/com/google/javascript/jscomp/js/es6/string/endswith.js @@ -16,6 +16,7 @@ 'require util/checkstringargs'; 'require util/polyfill'; +'require util/tointeger'; $jscomp.polyfill('String.prototype.endsWith', function(orig) { if (orig) return orig; @@ -34,8 +35,8 @@ $jscomp.polyfill('String.prototype.endsWith', function(orig) { 'use strict'; var string = $jscomp.checkStringArgs(this, searchString, 'endsWith'); searchString = searchString + ''; - if (opt_position === void 0) opt_position = string.length; - var i = Math.max(0, Math.min(opt_position | 0, string.length)); + var end = opt_position === void 0 ? string.length : $jscomp.toInteger(opt_position); + var i = Math.max(0, Math.min(end, string.length)); var j = searchString.length; while (j > 0 && i > 0) { if (string[--i] != searchString[--j]) return false; diff --git a/src/com/google/javascript/jscomp/js/es6/string/padend.js b/src/com/google/javascript/jscomp/js/es6/string/padend.js index d52a2ec6be9..c943b95a56f 100644 --- a/src/com/google/javascript/jscomp/js/es6/string/padend.js +++ b/src/com/google/javascript/jscomp/js/es6/string/padend.js @@ -15,8 +15,9 @@ */ 'require util/checkstringargs'; -'require util/stringpadding'; 'require util/polyfill'; +'require util/stringpadding'; +'require util/tolength'; $jscomp.polyfill('String.prototype.padEnd', function(orig) { if (orig) return orig; @@ -32,8 +33,9 @@ $jscomp.polyfill('String.prototype.padEnd', function(orig) { * @return {string} */ var padEnd = function(targetLength, opt_padString) { - var string = $jscomp.checkStringArgs(this, null, 'padStart'); - var padLength = targetLength - string.length; + 'use strict'; + var string = $jscomp.checkStringArgs(this, null, 'padEnd'); + var padLength = $jscomp.toLength(targetLength) - string.length; return string + $jscomp.stringPadding(opt_padString, padLength); }; diff --git a/src/com/google/javascript/jscomp/js/es6/string/padstart.js b/src/com/google/javascript/jscomp/js/es6/string/padstart.js index 42d5252c7da..4156cc87f4d 100644 --- a/src/com/google/javascript/jscomp/js/es6/string/padstart.js +++ b/src/com/google/javascript/jscomp/js/es6/string/padstart.js @@ -17,6 +17,7 @@ 'require util/checkstringargs'; 'require util/polyfill'; 'require util/stringpadding'; +'require util/tolength'; $jscomp.polyfill('String.prototype.padStart', function(orig) { if (orig) return orig; @@ -32,8 +33,9 @@ $jscomp.polyfill('String.prototype.padStart', function(orig) { * @return {string} */ var padStart = function(targetLength, opt_padString) { + 'use strict'; var string = $jscomp.checkStringArgs(this, null, 'padStart'); - var padLength = targetLength - string.length; + var padLength = $jscomp.toLength(targetLength) - string.length; return $jscomp.stringPadding(opt_padString, padLength) + string; }; diff --git a/src/com/google/javascript/jscomp/js/es6/string/repeat.js b/src/com/google/javascript/jscomp/js/es6/string/repeat.js index b8db822359c..13c5487bb97 100644 --- a/src/com/google/javascript/jscomp/js/es6/string/repeat.js +++ b/src/com/google/javascript/jscomp/js/es6/string/repeat.js @@ -16,6 +16,7 @@ 'require util/checkstringargs'; 'require util/polyfill'; +'require util/tointeger'; $jscomp.polyfill('String.prototype.repeat', function(orig) { if (orig) return orig; @@ -32,10 +33,10 @@ $jscomp.polyfill('String.prototype.repeat', function(orig) { var polyfill = function(copies) { 'use strict'; var string = $jscomp.checkStringArgs(this, null, 'repeat'); + copies = $jscomp.toInteger(copies); if (copies < 0 || copies > 0x4FFFFFFF) { // impose a 1GB limit throw new RangeError('Invalid count value'); } - copies = copies | 0; // cast to a signed integer. var result = ''; while (copies) { if (copies & 1) result += string; diff --git a/src/com/google/javascript/jscomp/js/es6/string/startswith.js b/src/com/google/javascript/jscomp/js/es6/string/startswith.js index e450c90ba2f..24f3cdb209c 100644 --- a/src/com/google/javascript/jscomp/js/es6/string/startswith.js +++ b/src/com/google/javascript/jscomp/js/es6/string/startswith.js @@ -16,6 +16,7 @@ 'require util/checkstringargs'; 'require util/polyfill'; +'require util/tointeger'; $jscomp.polyfill('String.prototype.startsWith', function(orig) { if (orig) return orig; @@ -38,7 +39,7 @@ $jscomp.polyfill('String.prototype.startsWith', function(orig) { var searchLen = searchString.length; var i = Math.max( 0, - Math.min(/** @type {number} */ (opt_position) | 0, string.length)); + Math.min($jscomp.toInteger(opt_position), string.length)); var j = 0; while (j < searchLen && i < strLen) { if (string[i++] != searchString[j++]) return false; diff --git a/src/com/google/javascript/jscomp/js/util/tointeger.js b/src/com/google/javascript/jscomp/js/util/tointeger.js new file mode 100644 index 00000000000..f93f58993b8 --- /dev/null +++ b/src/com/google/javascript/jscomp/js/util/tointeger.js @@ -0,0 +1,31 @@ +/* + * Copyright 2019 The Closure Compiler Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Converts argument to an integral numeric value. + * + * @see https://www.ecma-international.org/ecma-262/9.0/#sec-tointeger + * + * @param {*} arg + * @return {number} + */ +$jscomp.toInteger = function(arg) { + var n = Number(arg); + if (isNaN(n)) { + return 0; + } + return n > 0 ? Math.floor(n) : Math.ceil(n); +}; diff --git a/src/com/google/javascript/jscomp/js/util/tolength.js b/src/com/google/javascript/jscomp/js/util/tolength.js new file mode 100644 index 00000000000..4f4334e8226 --- /dev/null +++ b/src/com/google/javascript/jscomp/js/util/tolength.js @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Closure Compiler Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'require util/tointeger'; + +/** + * Converts argument to an integer suitable for use as the length of an array-like object. + * + * @see https://www.ecma-international.org/ecma-262/9.0/#sec-tolength + * + * @param {*} arg + * @return {number} + */ +$jscomp.toLength = function(arg) { + var len = $jscomp.toInteger(arg); + if (len < 0) { + return 0; + } + return Math.min(len, Math.pow(2, 53) - 1); +}; diff --git a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_copywithin_test.js b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_copywithin_test.js index b0b44a73a2b..a62f8800f5f 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_copywithin_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_copywithin_test.js @@ -91,6 +91,19 @@ testSuite({ assertObjectEquals([1, 3, 4, 4, 5], [1, 2, 3, 4, 5].copyWithin(1, 2, -1)); }, + testCopyWithin_infinity() { + assertObjectEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(Infinity, 1)); + assertObjectEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(1, Infinity)); + assertObjectEquals([2, 3, 4, 5, 5], [1, 2, 3, 4, 5].copyWithin(0, 1, Infinity)); + }, + + testCopyWithin_over32bit() { + var n = 2147483648; // 2**31 + assertObjectEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(n, 1)); + assertObjectEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(1, n)); + assertObjectEquals([2, 3, 4, 5, 5], [1, 2, 3, 4, 5].copyWithin(0, 1, n)); + }, + testCopyWithin_throwsIfNullish() { // TODO(tjgq): requires strict mode, lost in transpilation (b/24413211) // assertThrows(function() { diff --git a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_fill_test.js b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_fill_test.js index c3783dcfb0a..4f58c79a9a3 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_fill_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_fill_test.js @@ -60,6 +60,12 @@ testSuite({ assertObjectEquals([1, 2, 3], fill([1, 2, 3], 9, NaN, NaN)); assertObjectEquals([1, 2, 3], fill([1, 2, 3], 9, 1, NaN)); assertObjectEquals([9, 2, 3], fill([1, 2, 3], 9, NaN, 1)); + + assertObjectEquals([1, 1], fill([0, 0], 1, null)); + assertObjectEquals([0, 0], fill([0, 0], 1, 0, null)); + + assertObjectEquals([0, 1], fill([0, 0], 1, 1.1)); + assertObjectEquals([1, 0], fill([0, 0], 1, 0, 1.1)); }, testFill_arrayLike() { @@ -71,6 +77,12 @@ testSuite({ assertObjectEquals({0: 'z', 1: 'z', 2: 'y', 3: 'safe', length: 3}, arr); }, + testFill_arrayLikeCoerced() { + const arr = {length: 1.5, 0: 0, 1: 0}; + assertEquals(arr, Array.prototype.fill.call(arr, 'y')); + assertObjectEquals({0: 'y', 1: 0, length: 1.5}, arr); + }, + testFill_notArrayLike() { const arr = {2: 'x'}; // does nothing if no length assertEquals(arr, Array.prototype.fill.call(noCheck(arr), 'y', 0, 4)); diff --git a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_includes_test.js b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_includes_test.js index d198e232aa5..927aa6bf50f 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_includes_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/array_includes_test.js @@ -50,6 +50,13 @@ testSuite({ assertFalse(arr.includes(0, -3)); assertTrue(arr.includes(0, -4)); assertTrue(arr.includes(0, -5)); + assertTrue(arr.includes(1, true)); + var obj1 = { + valueOf: function() { + return 1; + } + }; + assertTrue(arr.includes(1, obj1)); // null and undefined // Make sure the compiler knows both null and undefined are allowed @@ -77,5 +84,29 @@ testSuite({ assertTrue(Array.prototype.includes.call(arr, 6)); assertFalse(Array.prototype.includes.call(arr, 5, 1)); assertFalse(Array.prototype.includes.call(arr, 7)); + + // length + arr = {length: 0.1, 0: 5, 1: 6}; + assertFalse(Array.prototype.includes.call(obj, 5)); + + // length boundary + var fromIndex = 9007199254740990; // 2 ** 53 - 2 + arr = { + 9007199254740990: 5, + 9007199254740991: 6 + }; + arr.length = 9007199254740991; + assertTrue(Array.prototype.includes.call(arr, 5, fromIndex)); + assertFalse(Array.prototype.includes.call(arr, 6, fromIndex)); + + arr.length = 9007199254740992; + assertTrue(Array.prototype.includes.call(arr, 5, fromIndex)); + assertFalse(Array.prototype.includes.call(arr, 6, fromIndex)); + + // nullish this + // TODO(tjgq): requires strict mode, lost in transpilation (b/24413211) + // assertThrows(function() { + // Array.prototype.includes.call(null); + // }); }, }); diff --git a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_codepointat_test.js b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_codepointat_test.js index 62c99c282c1..3951e8130d4 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_codepointat_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_codepointat_test.js @@ -76,6 +76,7 @@ testSuite({ assertUndefined('abc'.codePointAt(Infinity)); assertUndefined('abc'.codePointAt(-Infinity)); assertUndefined('abc'.codePointAt(-1)); + assertUndefined('abc'.codePointAt(Math.power(2, 32))); assertEquals(0x31, String.prototype.codePointAt.call(noCheck(14), 0)); assertEquals( diff --git a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_endswith_test.js b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_endswith_test.js index 7f0a8f53c84..ecaa518fc8d 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_endswith_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_endswith_test.js @@ -42,6 +42,9 @@ testSuite({ assertTrue('abc'.endsWith('', 1)); assertTrue('abc'.endsWith('', 2)); assertTrue('abc'.endsWith('', 3)); + + assertTrue('abc'.endsWith('c', Infinity)); + assertTrue('abc'.endsWith('c', 2147483648)); // 2**31 }, diff --git a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_fromcodepoint_test.js b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_fromcodepoint_test.js index 3cf611bbb37..d461debcba2 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_fromcodepoint_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_fromcodepoint_test.js @@ -58,6 +58,7 @@ testSuite({ assertFails(RangeError, () => String.fromCodePoint(noCheck({}))); assertFails(RangeError, () => String.fromCodePoint(NaN)); assertFails(RangeError, () => String.fromCodePoint(noCheck(/./))); + assertFails(RangeError, () => String.fromCodePoint(noCheck(undefined))); assertFails( Error, () => String.fromCodePoint(noCheck({valueOf() { throw Error(); }}))); diff --git a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_repeat_test.js b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_repeat_test.js index ef9aed5c995..b4c3145ba4e 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_repeat_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_repeat_test.js @@ -30,6 +30,7 @@ testSuite({ assertEquals('abababab', 'ab'.repeat(4)); assertEquals('', ''.repeat(5)); assertEquals('', 'ab'.repeat(0)); + assertEquals('', 'ab'.repeat(NaN)); assertEquals('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'a'.repeat(37)); assertEquals('\u10D8\u10D8\u10D8', '\u10D8'.repeat(3)); diff --git a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_startswith_test.js b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_startswith_test.js index 91f9753123b..bc06ce90afa 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_startswith_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/polyfill_tests/string_startswith_test.js @@ -66,6 +66,8 @@ testSuite({ assertTrue('xyz'.startsWith('x', -5)); assertFalse('xyz'.startsWith('x', noCheck('1'))); assertTrue('xyz'.startsWith('y', noCheck('1'))); + assertFalse('xyz'.startsWith('x', Infinity)); + assertFalse('xyz'.startsWith('x', 2147483648)); // 2**31 assertTrue('12345'.startsWith(noCheck(23), 1)); assertTrue('12345'.startsWith(noCheck(345), 2));