summaryrefslogtreecommitdiffstats
path: root/js/src/tests/ecma_2017/lastIndex-match-or-replace.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/ecma_2017/lastIndex-match-or-replace.js')
-rw-r--r--js/src/tests/ecma_2017/lastIndex-match-or-replace.js122
1 files changed, 122 insertions, 0 deletions
diff --git a/js/src/tests/ecma_2017/lastIndex-match-or-replace.js b/js/src/tests/ecma_2017/lastIndex-match-or-replace.js
new file mode 100644
index 000000000..b0a8b537c
--- /dev/null
+++ b/js/src/tests/ecma_2017/lastIndex-match-or-replace.js
@@ -0,0 +1,122 @@
+// RegExp.prototype[Symbol.match, Symbol.replace]: Test lastIndex changes for ES2017.
+
+// RegExp-like class to test the RegExp method slow paths.
+class DuckRegExp extends RegExp {
+ constructor(pattern, flags) {
+ return Object.create(DuckRegExp.prototype, {
+ regExp: {
+ value: new RegExp(pattern, flags)
+ },
+ lastIndex: {
+ value: 0, writable: true, enumerable: false, configurable: false
+ }
+ });
+ }
+
+ exec(...args) {
+ this.regExp.lastIndex = this.lastIndex;
+ try {
+ return this.regExp.exec(...args);
+ } finally {
+ if (this.global || this.sticky)
+ this.lastIndex = this.regExp.lastIndex;
+ }
+ }
+
+ get source() { return this.regExp.source; }
+
+ get global() { return this.regExp.global; }
+ get ignoreCase() { return this.regExp.ignoreCase; }
+ get multiline() { return this.regExp.multiline; }
+ get sticky() { return this.regExp.sticky; }
+ get unicode() { return this.regExp.unicode; }
+}
+
+// Test various combinations of:
+// - Pattern matches or doesn't match
+// - Global and/or sticky flag is set.
+// - lastIndex exceeds the input string length
+// - lastIndex is +-0
+const testCases = [
+ { regExp: /a/, lastIndex: 0, input: "a", result: 0 },
+ { regExp: /a/g, lastIndex: 0, input: "a", result: 0 },
+ { regExp: /a/y, lastIndex: 0, input: "a", result: 1 },
+
+ { regExp: /a/, lastIndex: 0, input: "b", result: 0 },
+ { regExp: /a/g, lastIndex: 0, input: "b", result: 0 },
+ { regExp: /a/y, lastIndex: 0, input: "b", result: 0 },
+
+ { regExp: /a/, lastIndex: -0, input: "a", result: -0 },
+ { regExp: /a/g, lastIndex: -0, input: "a", result: 0 },
+ { regExp: /a/y, lastIndex: -0, input: "a", result: 1 },
+
+ { regExp: /a/, lastIndex: -0, input: "b", result: -0 },
+ { regExp: /a/g, lastIndex: -0, input: "b", result: 0 },
+ { regExp: /a/y, lastIndex: -0, input: "b", result: 0 },
+
+ { regExp: /a/, lastIndex: -1, input: "a", result: -1 },
+ { regExp: /a/g, lastIndex: -1, input: "a", result: 0 },
+ { regExp: /a/y, lastIndex: -1, input: "a", result: 1 },
+
+ { regExp: /a/, lastIndex: -1, input: "b", result: -1 },
+ { regExp: /a/g, lastIndex: -1, input: "b", result: 0 },
+ { regExp: /a/y, lastIndex: -1, input: "b", result: 0 },
+
+ { regExp: /a/, lastIndex: 100, input: "a", result: 100 },
+ { regExp: /a/g, lastIndex: 100, input: "a", result: 0 },
+ { regExp: /a/y, lastIndex: 100, input: "a", result: 0 },
+];
+
+for (let method of [RegExp.prototype[Symbol.match], RegExp.prototype[Symbol.replace]]) {
+ for (let Constructor of [RegExp, DuckRegExp]) {
+ // Basic test.
+ for (let {regExp, lastIndex, input, result} of testCases) {
+ let re = new Constructor(regExp);
+ re.lastIndex = lastIndex;
+ Reflect.apply(method, re, [input]);
+ assertEq(re.lastIndex, result);
+ }
+
+ // Test when lastIndex is non-writable.
+ for (let {regExp, lastIndex, input} of testCases) {
+ let re = new Constructor(regExp);
+ Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false });
+ if (re.global || re.sticky) {
+ assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError);
+ } else {
+ Reflect.apply(method, re, [input]);
+ }
+ assertEq(re.lastIndex, lastIndex);
+ }
+
+ // Test when lastIndex is changed to non-writable as a side-effect.
+ for (let {regExp, lastIndex, input, result} of testCases) {
+ let re = new Constructor(regExp);
+ let called = false;
+ re.lastIndex = {
+ valueOf() {
+ assertEq(called, false);
+ called = true;
+ Object.defineProperty(re, "lastIndex", { value: 9000, writable: false });
+ return lastIndex;
+ }
+ };
+ if (re.sticky) {
+ assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError);
+ assertEq(called, true);
+ assertEq(re.lastIndex, 9000);
+ } else if (re.global) {
+ Reflect.apply(method, re, [input]);
+ assertEq(called, false);
+ assertEq(re.lastIndex, result);
+ } else {
+ Reflect.apply(method, re, [input]);
+ assertEq(called, true);
+ assertEq(re.lastIndex, 9000);
+ }
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);