Refactor String.prototype.replace (#3284)
This change brings the replace operation up to date with ES6 by implementing support for the @@replace well-known symbol, while also improving performance and memory usage. Also fixes #3070. JerryScript-DCO-1.0-Signed-off-by: Dániel Bátyai dbatyai@inf.u-szeged.hu
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
// Copyright JS Foundation and other contributors, http://js.foundation
|
||||
//
|
||||
// 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.
|
||||
|
||||
var r = new RegExp("([X]{6}|.*)", "g");
|
||||
var s = "a";
|
||||
s.replace(r, () => r.compile("[PqaCZlWQUT]{0}", "m"));
|
||||
@@ -0,0 +1,662 @@
|
||||
// Copyright JS Foundation and other contributors, http://js.foundation
|
||||
//
|
||||
// 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.
|
||||
|
||||
var replace = RegExp.prototype[Symbol.replace];
|
||||
|
||||
try {
|
||||
replace.call (0, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call (new RegExp(), {
|
||||
toString: () => {
|
||||
throw "abrupt string"
|
||||
}
|
||||
}, "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt string");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call (new RegExp(), "string", {
|
||||
toString: () => {
|
||||
throw "abrupt replace"
|
||||
}
|
||||
});
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt replace");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
get global() {
|
||||
throw "abrupt global"
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt global");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
global: true,
|
||||
set lastIndex(idx) {
|
||||
throw "abrupt lastIndex"
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt lastIndex");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
get exec() {
|
||||
throw "abrupt exec"
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt exec");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: RegExp.prototype.exec
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: 42
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: () => {
|
||||
throw "abrupt exec result"
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt exec result");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: () => {
|
||||
return 1
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: () => {
|
||||
return {
|
||||
get length() {
|
||||
throw "abrupt result length"
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt result length");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
global: true,
|
||||
exec: () => {
|
||||
return {
|
||||
length: 1,
|
||||
get 0() {
|
||||
throw "abrupt match"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"string",
|
||||
"replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt match");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
global: true,
|
||||
exec: () => {
|
||||
return {
|
||||
length: 1,
|
||||
get 0() {
|
||||
return {
|
||||
toString: () => {
|
||||
throw "abrupt match toString"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"string",
|
||||
"replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt match toString");
|
||||
}
|
||||
|
||||
var result_obj = {
|
||||
toString: () => {
|
||||
Object.defineProperty (result_obj, 'toString', {
|
||||
value: () => {
|
||||
throw "abrupt match toString delayed";
|
||||
}
|
||||
});
|
||||
return "str";
|
||||
}
|
||||
}
|
||||
|
||||
var first = true;
|
||||
try {
|
||||
replace.call ({
|
||||
global: true,
|
||||
exec: () => {
|
||||
if (!first) {
|
||||
return null;
|
||||
}
|
||||
|
||||
first = false;
|
||||
return {
|
||||
length: 1,
|
||||
get 0() {
|
||||
return result_obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"string",
|
||||
"replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt match toString delayed");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
global: true,
|
||||
get lastIndex() {
|
||||
throw "abrupt lastIndex get"
|
||||
},
|
||||
set lastIndex(i) {},
|
||||
exec: () => {
|
||||
return {
|
||||
length: 1,
|
||||
get 0() {
|
||||
return {
|
||||
toString: () => {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"string",
|
||||
"replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt lastIndex get");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
global: true,
|
||||
get lastIndex() {
|
||||
return {
|
||||
valueOf: () => {
|
||||
throw "abrupt lastIndex toNumber"
|
||||
}
|
||||
}
|
||||
},
|
||||
set lastIndex(i) {},
|
||||
exec: () => {
|
||||
return {
|
||||
length: 1,
|
||||
get 0() {
|
||||
return {
|
||||
toString: () => {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"string",
|
||||
"replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt lastIndex toNumber");
|
||||
}
|
||||
|
||||
var o = {
|
||||
global: true,
|
||||
exec: () => {
|
||||
return {
|
||||
length: 1,
|
||||
get 0() {
|
||||
return {
|
||||
toString: () => {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.defineProperty (o, 'lastIndex', {
|
||||
configurable: true,
|
||||
get: () => {
|
||||
Object.defineProperty (o, 'lastIndex', {
|
||||
get: () => {
|
||||
return {
|
||||
valueOf: () => {
|
||||
return 42
|
||||
}
|
||||
};
|
||||
},
|
||||
set: (i) => {
|
||||
throw "abrupt lastIndex put";
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
return {
|
||||
valueOf: () => {
|
||||
return 24
|
||||
}
|
||||
};
|
||||
},
|
||||
set: (i) => {}
|
||||
});
|
||||
|
||||
try {
|
||||
replace.call (o,
|
||||
"string",
|
||||
"replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt lastIndex put");
|
||||
}
|
||||
|
||||
o = {
|
||||
global: true,
|
||||
exec: () => {
|
||||
return {
|
||||
length: 1,
|
||||
get 0() {
|
||||
return {
|
||||
toString: () => {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
Object.defineProperty (o, 'lastIndex', {
|
||||
get: () => {
|
||||
Object.defineProperty (o, 'lastIndex', {
|
||||
value: 0,
|
||||
writable: false
|
||||
});
|
||||
return 0;
|
||||
},
|
||||
set: () => {}
|
||||
});
|
||||
|
||||
try {
|
||||
replace.call (o,
|
||||
"string",
|
||||
"replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
o = {
|
||||
global: true
|
||||
};
|
||||
Object.defineProperty (o, 'exec', {
|
||||
configurable: true,
|
||||
value: () => {
|
||||
Object.defineProperty (o, 'exec', {
|
||||
get: () => {
|
||||
throw "abrupt exec"
|
||||
},
|
||||
set: (v) => {}
|
||||
});
|
||||
return {
|
||||
length: 1,
|
||||
0: "thisisastring"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
replace.call (o,
|
||||
"string",
|
||||
"replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt exec");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: () => {
|
||||
return {
|
||||
length: 1,
|
||||
0: "str",
|
||||
get index() {
|
||||
throw "abrupt index"
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt index");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: () => {
|
||||
return {
|
||||
length: 1,
|
||||
0: "str",
|
||||
get index() {
|
||||
return {
|
||||
valueOf: () => {
|
||||
throw "abrupt index toNumber"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt index toNumber");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: () => {
|
||||
return {
|
||||
length: 2,
|
||||
0: "str",
|
||||
index: 0,
|
||||
get 1() {
|
||||
throw "abrupt capture"
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt capture");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: () => {
|
||||
return {
|
||||
length: 2,
|
||||
0: "str",
|
||||
index: 0,
|
||||
1: {
|
||||
toString: () => {
|
||||
throw "abrupt capture toString"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt capture toString");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: () => {
|
||||
return {
|
||||
length: 2,
|
||||
0: "str",
|
||||
index: 0,
|
||||
1: "st"
|
||||
}
|
||||
}
|
||||
}, "string", () => {
|
||||
throw "abrupt replace"
|
||||
});
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt replace");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call ({
|
||||
exec: () => {
|
||||
return {
|
||||
length: 2,
|
||||
0: "str",
|
||||
index: 0,
|
||||
1: "st"
|
||||
}
|
||||
}
|
||||
}, "string", () => {
|
||||
return {
|
||||
toString: () => {
|
||||
throw "abrupt replace toString"
|
||||
}
|
||||
}
|
||||
});
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt replace toString");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call (/abc/, "abc", () => {
|
||||
throw "fastpath abrupt replace"
|
||||
});
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "fastpath abrupt replace");
|
||||
}
|
||||
|
||||
try {
|
||||
replace.call (/abc/, "abc", () => {
|
||||
return {
|
||||
toString: () => {
|
||||
throw "fastpath abrupt replace"
|
||||
}
|
||||
}
|
||||
});
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "fastpath abrupt replace");
|
||||
}
|
||||
|
||||
assert (replace.call (/abc/, "abc", "xyz") === "xyz");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "xyz") === "abxyzfg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$$-") === "ab-$-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$&-") === "ab-cde-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$`-") === "ab-ab-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$'-") === "ab-fg-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$0-") === "ab-$0-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$1-") === "ab-c-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$2-") === "ab-d-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$3-") === "ab-d-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$4-") === "ab--fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$5-") === "ab-e-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$6-") === "ab-$6-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$00-") === "ab-$00-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$01-") === "ab-c-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$10-") === "ab-c0-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$99-") === "ab-$99-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "-$$1-") === "ab-$1-fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "$") === "ab$fg");
|
||||
assert (replace.call (/(c)((d)|(x))(e)/, "abcdefg", "$@") === "ab$@fg");
|
||||
|
||||
replace.call (/(c)((d)|(x))(e)/, "abcdefg", function () {
|
||||
assert (arguments[0] === "cde");
|
||||
assert (arguments[1] === "c");
|
||||
assert (arguments[2] === "d");
|
||||
assert (arguments[3] === "d");
|
||||
assert (arguments[4] === undefined);
|
||||
assert (arguments[5] === "e");
|
||||
assert (arguments[6] === 2);
|
||||
assert (arguments[7] === "abcdefg");
|
||||
});
|
||||
|
||||
var re = /ab/g;
|
||||
assert (replace.call (re, "-ab-ab-ab-ab-", "cd") === "-cd-cd-cd-cd-");
|
||||
assert (re.lastIndex === 0);
|
||||
|
||||
re.lastIndex = 5;
|
||||
assert (replace.call (re, "-ab-ab-ab-ab-", "cd") === "-cd-cd-cd-cd-");
|
||||
assert (re.lastIndex === 0);
|
||||
|
||||
assert (replace.call (/(?:)/g, "string", "Duck") === "DucksDucktDuckrDuckiDucknDuckgDuck");
|
||||
|
||||
class Regexplike {
|
||||
constructor() {
|
||||
this.index = 0;
|
||||
this.global = true;
|
||||
}
|
||||
|
||||
exec() {
|
||||
if (this.index > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.index = 39;
|
||||
var result = {
|
||||
length: 1,
|
||||
0: "Duck",
|
||||
index: this.index
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
re = new Regexplike();
|
||||
|
||||
/* Well-behaved RegExp-like object. */
|
||||
assert (replace.call (re, "What have you brought upon this cursed land", "$&") === "What have you brought upon this cursed Duck");
|
||||
|
||||
var replace_count = 0;
|
||||
|
||||
function replacer() {
|
||||
replace_count++;
|
||||
return arguments[0];
|
||||
}
|
||||
|
||||
re.index = 0;
|
||||
re.exec = function () {
|
||||
if (this.index > 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = {
|
||||
length: 1,
|
||||
0: "Duck",
|
||||
index: this.index++
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Mis-behaving RegExp-like object, replace function is called on each match, but the result is ignored for inconsistent matches. */
|
||||
assert (replace.call (re, "Badger", replacer) === "Ducker");
|
||||
assert (replace_count === 4);
|
||||
|
||||
re.index = 0;
|
||||
assert (replace.call (re, "Badger", "Ord") === "Order");
|
||||
|
||||
try {
|
||||
replace.call (RegExp.prototype, "string", "replace");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
/* Object with custom @@replace method */
|
||||
var o = {}
|
||||
o[Symbol.replace] = function () {
|
||||
return "Duck"
|
||||
};
|
||||
assert ("string".replace (o, "Mallard") === "Duck");
|
||||
|
||||
o[Symbol.replace] = 42;
|
||||
try {
|
||||
"string".replace (o, "Duck");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
Object.defineProperty (o, Symbol.replace, {
|
||||
get: () => {
|
||||
throw "abrupt @@replace get"
|
||||
},
|
||||
set: (v) => {}
|
||||
});
|
||||
|
||||
try {
|
||||
"string".replace (o, "Duck");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt @@replace get");
|
||||
}
|
||||
|
||||
o = {};
|
||||
o[Symbol.replace] = function () {
|
||||
throw "abrupt @@replace"
|
||||
};
|
||||
try {
|
||||
"string".replace (o, "str");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt @@replace")
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright JS Foundation and other contributors, http://js.foundation
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Changing exec should not affect replace.
|
||||
Object.getPrototypeOf(/x/).exec = function () { return 1234; }
|
||||
assert (/y/.exec("y") === 1234);
|
||||
assert ("y".replace (/y/, "x") === "x");
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
assert ("abcabc".replace("bc", ":") === "a:abc");
|
||||
assert ("hello".replace("", ":") === ":hello");
|
||||
assert ("hello".replace("h", "") === "ello");
|
||||
assert ("".replace("", "h") === "h");
|
||||
|
||||
assert ("xabcxabcx".replace (/abc/g, "[$&][$`][$']") === "x[abc][x][xabcx]x[abc][xabcx][x]x");
|
||||
assert ("abc".replace (/a(b)c|d()/, "[$1][$01][$2][$02][$99][$123][$012]") === "[b][b][][][$99][b23][b2]");
|
||||
@@ -110,10 +112,23 @@ try {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
// The real "exec" never returns with a number.
|
||||
Object.getPrototypeOf(/x/).exec = function () { return 1234; }
|
||||
try {
|
||||
"str".replace ({toString: function () {throw "abrupt search toString"}}, "");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt search toString");
|
||||
}
|
||||
|
||||
assert (/y/.exec("y") === 1234);
|
||||
try {
|
||||
"str".replace ("str", {toString: function () {throw "abrupt search toString"}});
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt search toString");
|
||||
}
|
||||
|
||||
// Changing exec should not affect replace.
|
||||
assert ("y".replace (/y/, "x") === "x");
|
||||
try {
|
||||
"str".replace ("str", function () {return {toString: function () {throw "abrupt replacer toString"}}});
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt replacer toString");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user