Source code
Revision control
Copy as Markdown
Other Tools
// RegExp.prototype[Symbol.search]: 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 testCasesNotPositiveZero = [
{ regExp: /a/, lastIndex: -1, input: "a" },
{ regExp: /a/g, lastIndex: -1, input: "a" },
{ regExp: /a/y, lastIndex: -1, input: "a" },
{ regExp: /a/, lastIndex: 100, input: "a" },
{ regExp: /a/g, lastIndex: 100, input: "a" },
{ regExp: /a/y, lastIndex: 100, input: "a" },
{ regExp: /a/, lastIndex: -1, input: "b" },
{ regExp: /a/g, lastIndex: -1, input: "b" },
{ regExp: /a/y, lastIndex: -1, input: "b" },
{ regExp: /a/, lastIndex: -0, input: "a" },
{ regExp: /a/g, lastIndex: -0, input: "a" },
{ regExp: /a/y, lastIndex: -0, input: "a" },
{ regExp: /a/, lastIndex: -0, input: "b" },
{ regExp: /a/g, lastIndex: -0, input: "b" },
{ regExp: /a/y, lastIndex: -0, input: "b" },
];
const testCasesPositiveZero = [
{ regExp: /a/, lastIndex: 0, input: "a" },
{ regExp: /a/g, lastIndex: 0, input: "a" },
{ regExp: /a/y, lastIndex: 0, input: "a" },
{ regExp: /a/, lastIndex: 0, input: "b" },
{ regExp: /a/g, lastIndex: 0, input: "b" },
{ regExp: /a/y, lastIndex: 0, input: "b" },
];
const testCases = [...testCasesNotPositiveZero, ...testCasesPositiveZero];
for (let Constructor of [RegExp, DuckRegExp]) {
// Basic test.
for (let {regExp, lastIndex, input} of testCases) {
let re = new Constructor(regExp);
re.lastIndex = lastIndex;
re[Symbol.search](input);
assertEq(re.lastIndex, lastIndex);
}
// Test when lastIndex is non-writable and not positive zero.
for (let {regExp, lastIndex, input} of testCasesNotPositiveZero) {
let re = new Constructor(regExp);
Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false });
assertThrowsInstanceOf(() => re[Symbol.search](input), TypeError);
assertEq(re.lastIndex, lastIndex);
}
// Test when lastIndex is non-writable and positive zero.
for (let {regExp, lastIndex, input} of testCasesPositiveZero) {
let re = new Constructor(regExp);
Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false });
if (re.global || re.sticky) {
assertThrowsInstanceOf(() => re[Symbol.search](input), TypeError);
} else {
re[Symbol.search](input);
}
assertEq(re.lastIndex, lastIndex);
}
// Test lastIndex isn't converted to a number.
for (let {regExp, lastIndex, input} of testCases) {
let re = new RegExp(regExp);
let badIndex = {
valueOf() {
assertEq(false, true);
}
};
re.lastIndex = badIndex;
re[Symbol.search](input);
assertEq(re.lastIndex, badIndex);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);