Simple ES6 feature:String prototype additions
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 19 Sep 2014 17:47:50 +0000 (17:47 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 19 Sep 2014 17:47:50 +0000 (17:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=131704

Patch by Diego Pino Garcia <dpino@igalia.com> on 2014-09-19
Reviewed by Darin Adler.

Source/JavaScriptCore:

* runtime/StringPrototype.cpp:
(JSC::StringPrototype::finishCreation):
(JSC::stringProtoFuncStartsWith): Added.
(JSC::stringProtoFuncEndsWith): Added.
(JSC::stringProtoFuncContains): Added.

Source/WTF:

* wtf/text/StringImpl.cpp:
(WTF::StringImpl::find):
(WTF::equalInner): Added.
(WTF::StringImpl::startsWith): Add implementation that supports
'startOffset' parameter.
(WTF::StringImpl::endsWith): Add implementation that supports
'endOffset' parameter.
* wtf/text/StringImpl.h:
* wtf/text/WTFString.h:
(WTF::String::contains): Modify current implementation to allow
setting a startOffset, 0 by default.
(WTF::String::startsWith):
(WTF::String::endsWith):

LayoutTests:

Test ES6 functions: string.startsWith(), string.endsWith() and
string.contains().

* js/Object-getOwnPropertyNames-expected.txt:
* js/script-tests/Object-getOwnPropertyNames.js:
* js/script-tests/string-contains.js: Added.
(stringToSearchIn.toString):
(startOffset.valueOf):
(matchString.toString):
(endOffset.valueOf):
* js/string-contains-expected.txt: Added.
* js/string-contains.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@173761 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
LayoutTests/ChangeLog
LayoutTests/js/Object-getOwnPropertyNames-expected.txt
LayoutTests/js/script-tests/Object-getOwnPropertyNames.js
LayoutTests/js/script-tests/string-contains.js [new file with mode: 0644]
LayoutTests/js/string-contains-expected.txt [new file with mode: 0644]
LayoutTests/js/string-contains.html [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/StringPrototype.cpp
Source/WTF/ChangeLog
Source/WTF/wtf/text/StringImpl.cpp
Source/WTF/wtf/text/StringImpl.h
Source/WTF/wtf/text/WTFString.h

index eae3979..5f0c0e6 100644 (file)
@@ -1,3 +1,23 @@
+2014-09-19  Diego Pino Garcia  <dpino@igalia.com>
+
+        Simple ES6 feature:String prototype additions
+        https://bugs.webkit.org/show_bug.cgi?id=131704
+
+        Reviewed by Darin Adler.
+
+        Test ES6 functions: string.startsWith(), string.endsWith() and
+        string.contains().
+
+        * js/Object-getOwnPropertyNames-expected.txt:
+        * js/script-tests/Object-getOwnPropertyNames.js:
+        * js/script-tests/string-contains.js: Added.
+        (stringToSearchIn.toString):
+        (startOffset.valueOf):
+        (matchString.toString):
+        (endOffset.valueOf):
+        * js/string-contains-expected.txt: Added.
+        * js/string-contains.html: Added.
+
 2014-09-19  Youenn Fablet  <youenn.fablet@crf.canon.fr>
 
         WTR and DRT didReceiveAuthenticationChallengeInFrame should print messages consistently
index bace26f..23b7ea9 100644 (file)
@@ -47,7 +47,7 @@ PASS getSortedOwnPropertyNames(Function.prototype) is ['apply', 'bind', 'call',
 PASS getSortedOwnPropertyNames(Array) is ['isArray', 'length', 'name', 'prototype']
 PASS getSortedOwnPropertyNames(Array.prototype) is ['concat', 'constructor', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'forEach', 'indexOf', 'join', 'keys', 'lastIndexOf', 'length', 'map', 'pop', 'push', 'reduce', 'reduceRight', 'reverse', 'shift', 'slice', 'some', 'sort', 'splice', 'toLocaleString', 'toString', 'unshift']
 PASS getSortedOwnPropertyNames(String) is ['fromCharCode', 'length', 'name', 'prototype']
-PASS getSortedOwnPropertyNames(String.prototype) is ['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'concat', 'constructor', 'fixed', 'fontcolor', 'fontsize', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'replace', 'search', 'slice', 'small', 'split', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimLeft', 'trimRight', 'valueOf']
+PASS getSortedOwnPropertyNames(String.prototype) is ['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'concat', 'constructor', 'contains', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'replace', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimLeft', 'trimRight', 'valueOf']
 PASS getSortedOwnPropertyNames(Boolean) is ['length', 'name', 'prototype']
 PASS getSortedOwnPropertyNames(Boolean.prototype) is ['constructor', 'toString', 'valueOf']
 PASS getSortedOwnPropertyNames(Number) is ['MAX_VALUE', 'MIN_VALUE', 'NEGATIVE_INFINITY', 'NaN', 'POSITIVE_INFINITY', 'length', 'name', 'prototype']
index b51ad61..375330d 100644 (file)
@@ -55,7 +55,7 @@ var expectedPropertyNamesSet = {
     "Array": "['isArray', 'length', 'name', 'prototype']",
     "Array.prototype": "['concat', 'constructor', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'forEach', 'indexOf', 'join', 'keys', 'lastIndexOf', 'length', 'map', 'pop', 'push', 'reduce', 'reduceRight', 'reverse', 'shift', 'slice', 'some', 'sort', 'splice', 'toLocaleString', 'toString', 'unshift']",
     "String": "['fromCharCode', 'length', 'name', 'prototype']",
-    "String.prototype": "['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'concat', 'constructor', 'fixed', 'fontcolor', 'fontsize', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'replace', 'search', 'slice', 'small', 'split', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimLeft', 'trimRight', 'valueOf']",
+    "String.prototype": "['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'concat', 'constructor', 'contains', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'replace', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimLeft', 'trimRight', 'valueOf']",
     "Boolean": "['length', 'name', 'prototype']",
     "Boolean.prototype": "['constructor', 'toString', 'valueOf']",
     "Number": "['MAX_VALUE', 'MIN_VALUE', 'NEGATIVE_INFINITY', 'NaN', 'POSITIVE_INFINITY', 'length', 'name', 'prototype']",
diff --git a/LayoutTests/js/script-tests/string-contains.js b/LayoutTests/js/script-tests/string-contains.js
new file mode 100644 (file)
index 0000000..918019e
--- /dev/null
@@ -0,0 +1,278 @@
+description("This test checks the ES6 string functions startsWith(), endsWith() and contains().");
+
+// Test contains
+shouldBe("'foo bar'.contains('bar')", "true");
+shouldBe("'foo bar'.contains('bar', 4)", "true");
+shouldBe("'foo bar'.contains('ar', 5)", "true");
+shouldBe("'foo bar'.contains('qux')", "false");
+shouldBe("'foo bar'.contains('foo')", "true");
+shouldBe("'foo bar'.contains('foo', 0)", "true");
+shouldBe("'foo bar'.contains('foo', -1)", "true");
+shouldBe("'foo bar'.contains('')", "true");
+shouldBe("'foo bar'.contains()", "false");
+shouldBe("'foo bar qux'.contains('qux', 7)", "true");
+shouldBe("'foo bar qux'.contains('bar', 7)", "false");
+shouldBe("'foo null bar'.contains()", "false");
+shouldBe("'foo null bar'.contains(null)", "true");
+shouldBe("'foo null bar'.contains(null)", "true");
+shouldBe("'foo undefined bar'.contains()", "true");
+shouldBe("'foo undefined bar'.contains(undefined)", "true");
+shouldBe("'foo undefined bar'.contains()", "true");
+shouldBe("'foo undefined bar'.contains()", "true");
+shouldBe("'foo true bar'.contains(true)", "true");
+shouldBe("'foo false bar'.contains(false)", "true");
+shouldBe("'foo 1 bar'.contains(1)", "true");
+shouldBe("'foo 1.1 bar'.contains(1.1)", "true");
+shouldBe("'foo NaN bar'.contains(NaN)", "true");
+shouldBe("'foo 1.0 bar'.contains(1.0)", "true");
+shouldBe("'foo 1e+100 bar'.contains(1e+100)", "true");
+shouldBe("'foo 1e100 bar'.contains(1e100)", "false");
+shouldBe("'フーバー'.contains('ーバ')", "true");
+shouldBe("'フーバー'.contains('クー')", "false");
+
+// Test startsWith
+shouldBe("'foo bar'.startsWith('foo')", "true");
+shouldBe("'foo bar'.startsWith('foo', 0)", "true");
+shouldBe("'foo bar'.startsWith('foo', -1)", "true");
+shouldBe("'foo bar'.startsWith('oo', 1)", "true");
+shouldBe("'foo bar'.startsWith('qux')", "false");
+shouldBe("'foo bar'.startsWith('')", "true");
+shouldBe("'foo bar'.startsWith()", "false");
+shouldBe("'null'.startsWith()", "false");
+shouldBe("'null'.startsWith(null)", "true");
+shouldBe("'null bar'.startsWith(null)", "true");
+shouldBe("'undefined'.startsWith()", "true");
+shouldBe("'undefined'.startsWith(undefined)", "true");
+shouldBe("'undefined bar'.startsWith()", "true");
+shouldBe("'undefined bar'.startsWith()", "true");
+shouldBe("'true bar'.startsWith(true)", "true");
+shouldBe("'false bar'.startsWith(false)", "true");
+shouldBe("'1 bar'.startsWith(1)", "true");
+shouldBe("'1.1 bar'.startsWith(1.1)", "true");
+shouldBe("'NaN bar'.startsWith(NaN)", "true");
+shouldBe("'1e+100 bar'.startsWith(1e+100)", "true");
+shouldBe("'1e100 bar'.startsWith(1e100)", "false");
+shouldBe("'フーバー'.startsWith('フー')", "true");
+shouldBe("'フーバー'.startsWith('バー')", "false");
+
+// Test endsWith
+shouldBe("'foo bar'.endsWith('bar')", "true");
+shouldBe("'foo bar'.endsWith('ba', 6)", "true");
+shouldBe("'foo bar'.endsWith(' ba', 6)", "true");
+shouldBe("'foo bar'.endsWith('foo bar')", "true");
+shouldBe("'foo bar'.endsWith('foo bar', 7)", "true");
+shouldBe("'foo bar'.endsWith('foo bar', 8)", "true");
+shouldBe("'foo bar'.endsWith('foo bar', -1)", "false");
+shouldBe("'foo bar'.endsWith('qux')", "false");
+shouldBe("'foo bar'.endsWith('')", "true");
+shouldBe("'foo bar'.endsWith()", "false");
+shouldBe("'foo null'.endsWith()", "false");
+shouldBe("'foo null'.endsWith(null)", "true");
+shouldBe("'foo null'.endsWith(null)", "true");
+shouldBe("'foo undefined'.endsWith()", "true");
+shouldBe("'foo undefined'.endsWith(undefined)", "true");
+shouldBe("'foo undefined'.endsWith()", "true");
+shouldBe("'foo undefined'.endsWith()", "true");
+shouldBe("'foo true'.endsWith(true)", "true");
+shouldBe("'foo false'.endsWith(false)", "true");
+shouldBe("'foo 1'.endsWith(1)", "true");
+shouldBe("'foo 1.1'.endsWith(1.1)", "true");
+shouldBe("'foo NaN'.endsWith(NaN)", "true");
+shouldBe("'foo 1e+100'.endsWith(1e+100)", "true");
+shouldBe("'foo 1e100'.endsWith(1e100)", "false");
+shouldBe("'フーバー'.endsWith('バー')", "true");
+shouldBe("'フーバー'.endsWith('フー')", "false");
+
+// Call functions with an environment record as 'this'.
+shouldThrow("(function() { var f = String.prototype.startsWith; (function() { f('a'); })(); })()");
+shouldThrow("(function() { var f = String.prototype.endsWith; (function() { f('a'); })(); })()");
+shouldThrow("(function() { var f = String.prototype.contains; (function() { f('a'); })(); })()");
+
+// ES6 spec says a regex as argument should throw an Exception.
+shouldThrow("'foo bar'.startsWith(/\w+/)");
+shouldThrow("'foo bar'.endsWith(/\w+/)");
+shouldThrow("'foo bar'.contains(/\w+/)");
+
+// Check side effects in startsWith.
+var sideEffect = "";
+var stringToSearchIn = new String("foo bar");
+stringToSearchIn.toString = function() {
+    sideEffect += "A";
+    return this;
+}
+var startOffset = new Number(0);
+startOffset.valueOf = function() {
+    sideEffect += "B";
+    return this;
+}
+var matchString = new String("foo");
+matchString.toString = function() {
+    sideEffect += "C";
+    return this;
+}
+// Calling stringToSearchIn.startsWith implicitly calls stringToSearchIn.toString(),
+// startOffset.valueOf() and matchString.toString(), in that respective order.
+shouldBe("stringToSearchIn.startsWith(matchString, startOffset)", "true");
+shouldBe("sideEffect == 'ABC'", "true");
+
+// If stringToSearchIn throws an exception startOffset.valueOf() and
+// matchString.toString() are not called.
+stringToSearchIn.toString = function() {
+    throw "error";
+}
+sideEffect = "";
+shouldThrow("stringToSearchIn.startsWith(matchString, startOffset)", "'error'");
+shouldBe("sideEffect == ''", "true");
+
+// If startOffset throws an exception stringToSearchIn.toString() is called but
+// matchString.toString() is not.
+stringToSearchIn.toString = function() {
+    sideEffect += "A";
+    return this;
+}
+startOffset.valueOf = function() {
+    throw "error";
+}
+sideEffect = "";
+shouldThrow("stringToSearchIn.startsWith(matchString, startOffset)", "'error'");
+shouldBe("sideEffect == 'A'", "true");
+
+// If matchString.toString() throws an exception stringToSearchIn.toString() and
+// startOffset.valueOf() were called.
+stringToSearchIn.toString = function() {
+    sideEffect += "A";
+    return this;
+}
+startOffset.valueOf = function() {
+    sideEffect += "B";
+    return this;
+}
+matchString.toString = function() {
+    throw "error";
+}
+sideEffect = "";
+shouldThrow("stringToSearchIn.startsWith(matchString, startOffset)", "'error'");
+shouldBe("sideEffect == 'AB'", "true");
+
+// Check side effects in endsWith.
+sideEffect = "";
+stringToSearchIn = new String('foo bar');
+stringToSearchIn.toString = function() {
+    sideEffect += "A";
+    return this;
+}
+var endOffset = new Number(stringToSearchIn.length);
+endOffset.valueOf = function() {
+    sideEffect += "B";
+    return this;
+}
+matchString = new String('bar');
+matchString.toString = function() {
+    sideEffect += "C";
+    return this;
+}
+
+// Calling stringToSearchIn.endsWith implicitly calls stringToSearchIn.toString(),
+// endOffset.valueOf() and matchString.toString(), in that respective order.
+shouldBe("stringToSearchIn.endsWith(matchString, endOffset)", "true");
+shouldBe("sideEffect == 'ABC'", "true");
+
+// If stringToSearchIn throws an exception endOffset.valueOf() and
+// matchString.toString() are not called.
+stringToSearchIn.toString = function() {
+    throw "error";
+}
+sideEffect = "";
+shouldThrow("stringToSearchIn.endsWith(matchString, endOffset)", "'error'");
+shouldBe("sideEffect == ''", "true");
+
+// If endOffset throws an exception stringToSearchIn.toString() is called but
+// matchString.toString() is not.
+stringToSearchIn.toString = function() {
+    sideEffect += "A";
+    return this;
+}
+endOffset.valueOf = function() {
+    throw "error";
+}
+sideEffect = "";
+shouldThrow("stringToSearchIn.endsWith(matchString, endOffset)", "'error'");
+shouldBe("sideEffect == 'A'", "true");
+
+// If matchString.toString() throws an exception stringToSearchIn.toString() and
+// endOffset.valueOf() were called.
+stringToSearchIn.toString = function() {
+    sideEffect += "A";
+    return this;
+}
+endOffset.valueOf = function() {
+    sideEffect += "B";
+    return this;
+}
+matchString.toString = function() {
+    throw "error";
+}
+sideEffect = "";
+shouldThrow("stringToSearchIn.endsWith(matchString, endOffset)", "'error'");
+shouldBe("sideEffect == 'AB'", "true");
+
+// Check side effects in contains.
+var sideEffect = "";
+stringToSearchIn = new String("foo bar");
+stringToSearchIn.toString = function() {
+    sideEffect += "A";
+    return this;
+}
+var startOffset = new Number(0);
+startOffset.valueOf = function() {
+    sideEffect += "B";
+    return this;
+}
+matchString = new String("foo");
+matchString.toString = function() {
+    sideEffect += "C";
+    return this;
+}
+// Calling stringToSearchIn.contains implicitly calls stringToSearchIn.toString(),
+// startOffset.valueOf() and matchString.toString(), in that respective order.
+shouldBe("stringToSearchIn.contains(matchString, startOffset)", "true");
+shouldBe("sideEffect == 'ABC'", "true");
+
+// If stringToSearchIn throws an exception startOffset.valueOf() and
+// matchString.toString() are not called.
+stringToSearchIn.toString = function() {
+    throw "error";
+}
+sideEffect = "";
+shouldThrow("stringToSearchIn.contains(matchString, startOffset)", "'error'");
+shouldBe("sideEffect == ''", "true");
+
+// If startOffset throws an exception stringToSearchIn.toString() is called but
+// matchString.toString() is not.
+stringToSearchIn.toString = function() {
+    sideEffect += "A";
+    return this;
+}
+startOffset.valueOf = function() {
+    throw "error";
+}
+sideEffect = "";
+shouldThrow("stringToSearchIn.contains(matchString, startOffset)", "'error'");
+shouldBe("sideEffect == 'A'", "true");
+
+// If matchString.toString() throws an exception stringToSearchIn.toString() and
+// startOffset.valueOf() were called.
+stringToSearchIn.toString = function() {
+    sideEffect += "A";
+    return this;
+}
+startOffset.valueOf = function() {
+    sideEffect += "B";
+    return this;
+}
+matchString.toString = function() {
+    throw "error";
+}
+sideEffect = "";
+shouldThrow("stringToSearchIn.contains(matchString, startOffset)", "'error'");
+shouldBe("sideEffect == 'AB'", "true");
diff --git a/LayoutTests/js/string-contains-expected.txt b/LayoutTests/js/string-contains-expected.txt
new file mode 100644 (file)
index 0000000..7c783a4
--- /dev/null
@@ -0,0 +1,116 @@
+This test checks the ES6 string functions startsWith(), endsWith() and contains().
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS 'foo bar'.contains('bar') is true
+PASS 'foo bar'.contains('bar', 4) is true
+PASS 'foo bar'.contains('ar', 5) is true
+PASS 'foo bar'.contains('qux') is false
+PASS 'foo bar'.contains('foo') is true
+PASS 'foo bar'.contains('foo', 0) is true
+PASS 'foo bar'.contains('foo', -1) is true
+PASS 'foo bar'.contains('') is true
+PASS 'foo bar'.contains() is false
+PASS 'foo bar qux'.contains('qux', 7) is true
+PASS 'foo bar qux'.contains('bar', 7) is false
+PASS 'foo null bar'.contains() is false
+PASS 'foo null bar'.contains(null) is true
+PASS 'foo null bar'.contains(null) is true
+PASS 'foo undefined bar'.contains() is true
+PASS 'foo undefined bar'.contains(undefined) is true
+PASS 'foo undefined bar'.contains() is true
+PASS 'foo undefined bar'.contains() is true
+PASS 'foo true bar'.contains(true) is true
+PASS 'foo false bar'.contains(false) is true
+PASS 'foo 1 bar'.contains(1) is true
+PASS 'foo 1.1 bar'.contains(1.1) is true
+PASS 'foo NaN bar'.contains(NaN) is true
+PASS 'foo 1.0 bar'.contains(1.0) is true
+PASS 'foo 1e+100 bar'.contains(1e+100) is true
+PASS 'foo 1e100 bar'.contains(1e100) is false
+PASS 'フーバー'.contains('ーバ') is true
+PASS 'フーバー'.contains('クー') is false
+PASS 'foo bar'.startsWith('foo') is true
+PASS 'foo bar'.startsWith('foo', 0) is true
+PASS 'foo bar'.startsWith('foo', -1) is true
+PASS 'foo bar'.startsWith('oo', 1) is true
+PASS 'foo bar'.startsWith('qux') is false
+PASS 'foo bar'.startsWith('') is true
+PASS 'foo bar'.startsWith() is false
+PASS 'null'.startsWith() is false
+PASS 'null'.startsWith(null) is true
+PASS 'null bar'.startsWith(null) is true
+PASS 'undefined'.startsWith() is true
+PASS 'undefined'.startsWith(undefined) is true
+PASS 'undefined bar'.startsWith() is true
+PASS 'undefined bar'.startsWith() is true
+PASS 'true bar'.startsWith(true) is true
+PASS 'false bar'.startsWith(false) is true
+PASS '1 bar'.startsWith(1) is true
+PASS '1.1 bar'.startsWith(1.1) is true
+PASS 'NaN bar'.startsWith(NaN) is true
+PASS '1e+100 bar'.startsWith(1e+100) is true
+PASS '1e100 bar'.startsWith(1e100) is false
+PASS 'フーバー'.startsWith('フー') is true
+PASS 'フーバー'.startsWith('バー') is false
+PASS 'foo bar'.endsWith('bar') is true
+PASS 'foo bar'.endsWith('ba', 6) is true
+PASS 'foo bar'.endsWith(' ba', 6) is true
+PASS 'foo bar'.endsWith('foo bar') is true
+PASS 'foo bar'.endsWith('foo bar', 7) is true
+PASS 'foo bar'.endsWith('foo bar', 8) is true
+PASS 'foo bar'.endsWith('foo bar', -1) is false
+PASS 'foo bar'.endsWith('qux') is false
+PASS 'foo bar'.endsWith('') is true
+PASS 'foo bar'.endsWith() is false
+PASS 'foo null'.endsWith() is false
+PASS 'foo null'.endsWith(null) is true
+PASS 'foo null'.endsWith(null) is true
+PASS 'foo undefined'.endsWith() is true
+PASS 'foo undefined'.endsWith(undefined) is true
+PASS 'foo undefined'.endsWith() is true
+PASS 'foo undefined'.endsWith() is true
+PASS 'foo true'.endsWith(true) is true
+PASS 'foo false'.endsWith(false) is true
+PASS 'foo 1'.endsWith(1) is true
+PASS 'foo 1.1'.endsWith(1.1) is true
+PASS 'foo NaN'.endsWith(NaN) is true
+PASS 'foo 1e+100'.endsWith(1e+100) is true
+PASS 'foo 1e100'.endsWith(1e100) is false
+PASS 'フーバー'.endsWith('バー') is true
+PASS 'フーバー'.endsWith('フー') is false
+PASS (function() { var f = String.prototype.startsWith; (function() { f('a'); })(); })() threw exception TypeError: Type error.
+PASS (function() { var f = String.prototype.endsWith; (function() { f('a'); })(); })() threw exception TypeError: Type error.
+PASS (function() { var f = String.prototype.contains; (function() { f('a'); })(); })() threw exception TypeError: Type error.
+PASS 'foo bar'.startsWith(/w+/) threw exception TypeError: Type error.
+PASS 'foo bar'.endsWith(/w+/) threw exception TypeError: Type error.
+PASS 'foo bar'.contains(/w+/) threw exception TypeError: Type error.
+PASS stringToSearchIn.startsWith(matchString, startOffset) is true
+PASS sideEffect == 'ABC' is true
+PASS stringToSearchIn.startsWith(matchString, startOffset) threw exception error.
+PASS sideEffect == '' is true
+PASS stringToSearchIn.startsWith(matchString, startOffset) threw exception error.
+PASS sideEffect == 'A' is true
+PASS stringToSearchIn.startsWith(matchString, startOffset) threw exception error.
+PASS sideEffect == 'AB' is true
+PASS stringToSearchIn.endsWith(matchString, endOffset) is true
+PASS sideEffect == 'ABC' is true
+PASS stringToSearchIn.endsWith(matchString, endOffset) threw exception error.
+PASS sideEffect == '' is true
+PASS stringToSearchIn.endsWith(matchString, endOffset) threw exception error.
+PASS sideEffect == 'A' is true
+PASS stringToSearchIn.endsWith(matchString, endOffset) threw exception error.
+PASS sideEffect == 'AB' is true
+PASS stringToSearchIn.contains(matchString, startOffset) is true
+PASS sideEffect == 'ABC' is true
+PASS stringToSearchIn.contains(matchString, startOffset) threw exception error.
+PASS sideEffect == '' is true
+PASS stringToSearchIn.contains(matchString, startOffset) threw exception error.
+PASS sideEffect == 'A' is true
+PASS stringToSearchIn.contains(matchString, startOffset) threw exception error.
+PASS sideEffect == 'AB' is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/string-contains.html b/LayoutTests/js/string-contains.html
new file mode 100644 (file)
index 0000000..b716576
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+  <script src="script-tests/string-contains.js"></script>
+  <script src="../resources/js-test-post.js"></script>
+</body>
+</html>
index 412edf5..20ef433 100644 (file)
@@ -1,3 +1,16 @@
+2014-09-19  Diego Pino Garcia  <dpino@igalia.com>
+
+        Simple ES6 feature:String prototype additions
+        https://bugs.webkit.org/show_bug.cgi?id=131704
+
+        Reviewed by Darin Adler.
+
+        * runtime/StringPrototype.cpp:
+        (JSC::StringPrototype::finishCreation):
+        (JSC::stringProtoFuncStartsWith): Added.
+        (JSC::stringProtoFuncEndsWith): Added.
+        (JSC::stringProtoFuncContains): Added.
+
 2014-09-18  Joseph Pecoraro  <pecoraro@apple.com>
 
         Unreviewed rollout r173731. Broke multiple builds.
index 8c5b976..0e39a39 100644 (file)
@@ -82,6 +82,9 @@ EncodedJSValue JSC_HOST_CALL stringProtoFuncLink(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimLeft(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimRight(ExecState*);
+EncodedJSValue JSC_HOST_CALL stringProtoFuncStartsWith(ExecState*);
+EncodedJSValue JSC_HOST_CALL stringProtoFuncEndsWith(ExecState*);
+EncodedJSValue JSC_HOST_CALL stringProtoFuncContains(ExecState*);
 
 const ClassInfo StringPrototype::s_info = { "String", &StringObject::s_info, 0, CREATE_METHOD_TABLE(StringPrototype) };
 
@@ -131,6 +134,9 @@ void StringPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject, JSStr
     JSC_NATIVE_FUNCTION("trim", stringProtoFuncTrim, DontEnum, 0);
     JSC_NATIVE_FUNCTION("trimLeft", stringProtoFuncTrimLeft, DontEnum, 0);
     JSC_NATIVE_FUNCTION("trimRight", stringProtoFuncTrimRight, DontEnum, 0);
+    JSC_NATIVE_FUNCTION("startsWith", stringProtoFuncStartsWith, DontEnum, 0);
+    JSC_NATIVE_FUNCTION("endsWith", stringProtoFuncEndsWith, DontEnum, 0);
+    JSC_NATIVE_FUNCTION("contains", stringProtoFuncContains, DontEnum, 0);
 
     // The constructor will be added later, after StringConstructor has been built
     putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(0), DontDelete | ReadOnly | DontEnum);
@@ -1546,6 +1552,77 @@ EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimRight(ExecState* exec)
     JSValue thisValue = exec->thisValue();
     return JSValue::encode(trimString(exec, thisValue, TrimRight));
 }
-    
+
+EncodedJSValue JSC_HOST_CALL stringProtoFuncStartsWith(ExecState* exec)
+{
+    JSValue thisValue = exec->thisValue();
+    if (!checkObjectCoercible(thisValue))
+        return throwVMTypeError(exec);
+
+    String stringToSearchIn = thisValue.toString(exec)->value(exec);
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+
+    JSValue a0 = exec->argument(0);
+    if (jsDynamicCast<RegExpObject*>(a0))
+        return throwVMTypeError(exec);
+
+    unsigned start = std::max(0, exec->argument(1).toInt32(exec));
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+
+    String matchString = a0.toString(exec)->value(exec);
+
+    return JSValue::encode(jsBoolean(stringToSearchIn.startsWith(matchString, start, true)));
+}
+
+EncodedJSValue JSC_HOST_CALL stringProtoFuncEndsWith(ExecState* exec)
+{
+    JSValue thisValue = exec->thisValue();
+    if (!checkObjectCoercible(thisValue))
+        return throwVMTypeError(exec);
+
+    String stringToSearchIn = thisValue.toString(exec)->value(exec);
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+
+    JSValue a0 = exec->argument(0);
+    if (jsDynamicCast<RegExpObject*>(a0))
+        return throwVMTypeError(exec);
+
+    unsigned length = stringToSearchIn.length();
+    JSValue a1 = exec->argument(1);
+    int pos = a1.isUndefined() ? length : a1.toInt32(exec);
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+    unsigned end = std::min<unsigned>(std::max(pos, 0), length);
+
+    String matchString = a0.toString(exec)->value(exec);
+
+    return JSValue::encode(jsBoolean(stringToSearchIn.endsWith(matchString, end, true)));
+}
+
+EncodedJSValue JSC_HOST_CALL stringProtoFuncContains(ExecState* exec)
+{
+    JSValue thisValue = exec->thisValue();
+    if (!checkObjectCoercible(thisValue))
+        return throwVMTypeError(exec);
+
+    String stringToSearchIn = thisValue.toString(exec)->value(exec);
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+
+    JSValue a0 = exec->argument(0);
+    if (jsDynamicCast<RegExpObject*>(a0))
+        return throwVMTypeError(exec);
+
+    unsigned start = std::max(0, exec->argument(1).toInt32(exec));
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+
+    String matchString = a0.toString(exec)->value(exec);
+
+    return JSValue::encode(jsBoolean(stringToSearchIn.contains(matchString, true, start)));
+}
     
 } // namespace JSC
index 7d6976a..1ae583f 100644 (file)
@@ -1,3 +1,24 @@
+2014-09-19  Diego Pino Garcia  <dpino@igalia.com>
+
+        Simple ES6 feature:String prototype additions
+        https://bugs.webkit.org/show_bug.cgi?id=131704
+
+        Reviewed by Darin Adler.
+
+        * wtf/text/StringImpl.cpp:
+        (WTF::StringImpl::find):
+        (WTF::equalInner): Added.
+        (WTF::StringImpl::startsWith): Add implementation that supports
+        'startOffset' parameter.
+        (WTF::StringImpl::endsWith): Add implementation that supports
+        'endOffset' parameter.
+        * wtf/text/StringImpl.h:
+        * wtf/text/WTFString.h:
+        (WTF::String::contains): Modify current implementation to allow
+        setting a startOffset, 0 by default.
+        (WTF::String::startsWith):
+        (WTF::String::endsWith):
+
 2014-09-18  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [GTK] Dot not allow to create delete-on-destroy GMainLoopSources
index 6aff9b2..ca54405 100644 (file)
@@ -1368,6 +1368,25 @@ ALWAYS_INLINE static bool equalInner(const StringImpl* stringImpl, unsigned star
     return equalIgnoringCase(stringImpl->characters16() + startOffset, reinterpret_cast<const LChar*>(matchString), matchLength);
 }
 
+ALWAYS_INLINE static bool equalInner(StringImpl& stringImpl, unsigned startOffset, StringImpl& matchString, bool caseSensitive)
+{
+    if (startOffset > stringImpl.length())
+        return false;
+    if (matchString.length() > stringImpl.length())
+        return false;
+    if (matchString.length() + startOffset > stringImpl.length())
+        return false;
+
+    if (caseSensitive) {
+        if (stringImpl.is8Bit())
+            return equal(stringImpl.characters8() + startOffset, matchString.characters8(), matchString.length());
+        return equal(stringImpl.characters16() + startOffset, matchString.characters16(), matchString.length());
+    }
+    if (stringImpl.is8Bit())
+        return equalIgnoringCase(stringImpl.characters8() + startOffset, matchString.characters8(), matchString.length());
+    return equalIgnoringCase(stringImpl.characters16() + startOffset, matchString.characters16(), matchString.length());
+}
+
 bool StringImpl::startsWith(const StringImpl* str) const
 {
     if (!str)
@@ -1399,6 +1418,11 @@ bool StringImpl::startsWith(const char* matchString, unsigned matchLength, bool
     return equalInner(this, 0, matchString, matchLength, caseSensitive);
 }
 
+bool StringImpl::startsWith(StringImpl& matchString, unsigned startOffset, bool caseSensitive) const
+{
+    return equalInner(const_cast<StringImpl&>(*this), startOffset, matchString, caseSensitive);
+}
+
 bool StringImpl::endsWith(StringImpl* matchString, bool caseSensitive)
 {
     ASSERT(matchString);
@@ -1423,6 +1447,13 @@ bool StringImpl::endsWith(const char* matchString, unsigned matchLength, bool ca
     return equalInner(this, startOffset, matchString, matchLength, caseSensitive);
 }
 
+bool StringImpl::endsWith(StringImpl& matchString, unsigned endOffset, bool caseSensitive) const
+{
+    if (endOffset < matchString.length())
+        return false;
+    return equalInner(const_cast<StringImpl&>(*this), endOffset - matchString.length(), matchString, caseSensitive);
+}
+
 PassRef<StringImpl> StringImpl::replace(UChar oldC, UChar newC)
 {
     if (oldC == newC)
index 8f695b4..19becf5 100644 (file)
@@ -673,12 +673,14 @@ public:
     WTF_EXPORT_STRING_API bool startsWith(const char*, unsigned matchLength, bool caseSensitive) const;
     template<unsigned matchLength>
     bool startsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const { return startsWith(prefix, matchLength - 1, caseSensitive); }
+    WTF_EXPORT_STRING_API bool startsWith(StringImpl&, unsigned startOffset, bool caseSensitive) const;
 
     WTF_EXPORT_STRING_API bool endsWith(StringImpl*, bool caseSensitive = true);
     WTF_EXPORT_STRING_API bool endsWith(UChar) const;
     WTF_EXPORT_STRING_API bool endsWith(const char*, unsigned matchLength, bool caseSensitive) const;
     template<unsigned matchLength>
     bool endsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const { return endsWith(prefix, matchLength - 1, caseSensitive); }
+    WTF_EXPORT_STRING_API bool endsWith(StringImpl&, unsigned endOffset, bool caseSensitive) const;
 
     WTF_EXPORT_STRING_API PassRef<StringImpl> replace(UChar, UChar);
     WTF_EXPORT_STRING_API PassRef<StringImpl> replace(UChar, StringImpl*);
index 6850416..f51a3c8 100644 (file)
@@ -256,8 +256,10 @@ public:
     WTF_EXPORT_STRING_API UChar32 characterStartingAt(unsigned) const; // Ditto.
     
     bool contains(UChar c) const { return find(c) != notFound; }
-    bool contains(const LChar* str, bool caseSensitive = true) const { return find(str, 0, caseSensitive) != notFound; }
-    bool contains(const String& str, bool caseSensitive = true) const { return find(str, 0, caseSensitive) != notFound; }
+    bool contains(const LChar* str, bool caseSensitive = true, unsigned startOffset = 0) const 
+        { return find(str, startOffset, caseSensitive) != notFound; }
+    bool contains(const String& str, bool caseSensitive = true, unsigned startOffset = 0) const 
+        { return find(str, startOffset, caseSensitive) != notFound; }
 
     bool startsWith(const String& s) const
         { return m_impl ? m_impl->startsWith(s.impl()) : s.isEmpty(); }
@@ -268,6 +270,8 @@ public:
     template<unsigned matchLength>
     bool startsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const
         { return m_impl ? m_impl->startsWith<matchLength>(prefix, caseSensitive) : !matchLength; }
+    bool startsWith(String& prefix, unsigned startOffset, bool caseSensitive) const
+        { return m_impl && prefix.impl() ? m_impl->startsWith(*prefix.impl(), startOffset, caseSensitive) : false; }
 
     bool endsWith(const String& s, bool caseSensitive = true) const
         { return m_impl ? m_impl->endsWith(s.impl(), caseSensitive) : s.isEmpty(); }
@@ -277,6 +281,8 @@ public:
     template<unsigned matchLength>
     bool endsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const
         { return m_impl ? m_impl->endsWith<matchLength>(prefix, caseSensitive) : !matchLength; }
+    bool endsWith(String& suffix, unsigned endOffset, bool caseSensitive) const
+        { return m_impl && suffix.impl() ? m_impl->endsWith(*suffix.impl(), endOffset, caseSensitive) : false; }
 
     WTF_EXPORT_STRING_API void append(const String&);
     WTF_EXPORT_STRING_API void append(LChar);