Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify the wrapRegExp helper for named groups #13201

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ module.exports = {
rules: {
"no-var": "off",
"comma-dangle": "off",
"no-func-assign": "off",
"import/no-extraneous-dependencies": "off",
},
},
{
Expand Down
8 changes: 8 additions & 0 deletions packages/babel-helpers/src/helpers-generated.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 0 additions & 73 deletions packages/babel-helpers/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2021,76 +2021,3 @@ if (!process.env.BABEL_8_BREAKING) {
}
`;
}

helpers.wrapRegExp = helper("7.2.6")`
import wrapNativeSuper from "wrapNativeSuper";
import getPrototypeOf from "getPrototypeOf";
import possibleConstructorReturn from "possibleConstructorReturn";
import inherits from "inherits";

export default function _wrapRegExp(re, groups) {
_wrapRegExp = function(re, groups) {
return new BabelRegExp(re, undefined, groups);
};

var _RegExp = wrapNativeSuper(RegExp);
var _super = RegExp.prototype;
var _groups = new WeakMap();

function BabelRegExp(re, flags, groups) {
var _this = _RegExp.call(this, re, flags);
// if the regex is recreated with 'g' flag
_groups.set(_this, groups || _groups.get(re));
return _this;
}
inherits(BabelRegExp, _RegExp);

BabelRegExp.prototype.exec = function(str) {
var result = _super.exec.call(this, str);
if (result) result.groups = buildGroups(result, this);
return result;
};
BabelRegExp.prototype[Symbol.replace] = function(str, substitution) {
if (typeof substitution === "string") {
var groups = _groups.get(this);
return _super[Symbol.replace].call(
this,
str,
substitution.replace(/\\$<([^>]+)>/g, function(_, name) {
return "$" + groups[name];
})
);
} else if (typeof substitution === "function") {
var _this = this;
return _super[Symbol.replace].call(
this,
str,
function() {
var args = [];
args.push.apply(args, arguments);
if (typeof args[args.length - 1] !== "object") {
// Modern engines already pass result.groups as the last arg.
args.push(buildGroups(args, _this));
}
return substitution.apply(this, args);
}
);
} else {
return _super[Symbol.replace].call(this, str, substitution);
}
}

function buildGroups(result, re) {
// NOTE: This function should return undefined if there are no groups,
// but in that case Babel doesn't add the wrapper anyway.

var g = _groups.get(re);
return Object.keys(g).reduce(function(groups, name) {
groups[name] = result[g[name]];
return groups;
}, Object.create(null));
}

return _wrapRegExp.apply(this, arguments);
}
`;
65 changes: 65 additions & 0 deletions packages/babel-helpers/src/helpers/wrapRegExp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* @minVersion 7.2.6 */

import setPrototypeOf from "setPrototypeOf";
import inherits from "inherits";

export default function _wrapRegExp() {
_wrapRegExp = function (re, groups) {
return new BabelRegExp(re, undefined, groups);
};

var _super = RegExp.prototype;
var _groups = new WeakMap();

function BabelRegExp(re, flags, groups) {
var _this = new RegExp(re, flags);
// if the regex is recreated with 'g' flag
_groups.set(_this, groups || _groups.get(re));
return setPrototypeOf(_this, BabelRegExp.prototype);
}
inherits(BabelRegExp, RegExp);

BabelRegExp.prototype.exec = function (str) {
var result = _super.exec.call(this, str);
if (result) result.groups = buildGroups(result, this);
return result;
};
BabelRegExp.prototype[Symbol.replace] = function (str, substitution) {
if (typeof substitution === "string") {
var groups = _groups.get(this);
return _super[Symbol.replace].call(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We can also hoist _super[Symbol.replace] if we want to golf a bit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not caching it makes it possible to monkey-patch RegExp.protototype[Symbol.replace], similarly to how its possible with native regexps.

this,
str,
substitution.replace(/\$<([^>]+)>/g, function (_, name) {
return "$" + groups[name];
})
);
} else if (typeof substitution === "function") {
var _this = this;
return _super[Symbol.replace].call(this, str, function () {
var args = [];
args.push.apply(args, arguments);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
var args = [];
args.push.apply(args, arguments);
var args = [].slice.call(arguments);

if (typeof args[args.length - 1] !== "object") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is incorrect. Because we transform (?<year>\d{4}) to ([0-9]{4}), if there is a object here, it won't include the year group. I think we ned to pop and push if there's an object, and only push if there's not.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The object here is not the one internally built by the engine, but it's the .groups property returned by our exec() (which defined .groups.year.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can verify it with

class MyRegExp extends RegExp {
  exec() {
    return Object.assign(["a", "b"], { groups: { name: "c" } });
  }
  [Symbol.replace](str, subst) {
    super[Symbol.replace](str, (...args) => {
      console.log(args);
      return "_";
    });
  }
}
"abc".replace(new MyRegExp("b"));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crazy, I didn't realize it was accessing the .groups directly from result.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more perf reason that makes implementers hate regexp subclasses 😛

// Modern engines already pass result.groups as the last arg.
args.push(buildGroups(args, _this));
}
return substitution.apply(this, args);
});
} else {
return _super[Symbol.replace].call(this, str, substitution);
}
};

function buildGroups(result, re) {
// NOTE: This function should return undefined if there are no groups,
// but in that case Babel doesn't add the wrapper anyway.

var g = _groups.get(re);
return Object.keys(g).reduce(function (groups, name) {
groups[name] = result[g[name]];
return groups;
}, Object.create(null));
}

return _wrapRegExp.apply(this, arguments);
}
18 changes: 9 additions & 9 deletions packages/babel-runtime-corejs2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@
"./helpers/typeof.js"
],
"./helpers/esm/typeof": "./helpers/esm/typeof.js",
"./helpers/wrapRegExp": [
{
"node": "./helpers/wrapRegExp.js",
"import": "./helpers/esm/wrapRegExp.js",
"default": "./helpers/wrapRegExp.js"
},
"./helpers/wrapRegExp.js"
],
"./helpers/esm/wrapRegExp": "./helpers/esm/wrapRegExp.js",
"./helpers/asyncIterator": [
{
"node": "./helpers/asyncIterator.js",
Expand Down Expand Up @@ -801,15 +810,6 @@
"./helpers/classPrivateMethodSet.js"
],
"./helpers/esm/classPrivateMethodSet": "./helpers/esm/classPrivateMethodSet.js",
"./helpers/wrapRegExp": [
{
"node": "./helpers/wrapRegExp.js",
"import": "./helpers/esm/wrapRegExp.js",
"default": "./helpers/wrapRegExp.js"
},
"./helpers/wrapRegExp.js"
],
"./helpers/esm/wrapRegExp": "./helpers/esm/wrapRegExp.js",
"./package": "./package.json",
"./package.json": "./package.json",
"./regenerator": "./regenerator/index.js",
Expand Down
18 changes: 9 additions & 9 deletions packages/babel-runtime-corejs3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@
"./helpers/typeof.js"
],
"./helpers/esm/typeof": "./helpers/esm/typeof.js",
"./helpers/wrapRegExp": [
{
"node": "./helpers/wrapRegExp.js",
"import": "./helpers/esm/wrapRegExp.js",
"default": "./helpers/wrapRegExp.js"
},
"./helpers/wrapRegExp.js"
],
"./helpers/esm/wrapRegExp": "./helpers/esm/wrapRegExp.js",
"./helpers/asyncIterator": [
{
"node": "./helpers/asyncIterator.js",
Expand Down Expand Up @@ -800,15 +809,6 @@
"./helpers/classPrivateMethodSet.js"
],
"./helpers/esm/classPrivateMethodSet": "./helpers/esm/classPrivateMethodSet.js",
"./helpers/wrapRegExp": [
{
"node": "./helpers/wrapRegExp.js",
"import": "./helpers/esm/wrapRegExp.js",
"default": "./helpers/wrapRegExp.js"
},
"./helpers/wrapRegExp.js"
],
"./helpers/esm/wrapRegExp": "./helpers/esm/wrapRegExp.js",
"./package": "./package.json",
"./package.json": "./package.json",
"./regenerator": "./regenerator/index.js",
Expand Down
18 changes: 9 additions & 9 deletions packages/babel-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@
"./helpers/typeof.js"
],
"./helpers/esm/typeof": "./helpers/esm/typeof.js",
"./helpers/wrapRegExp": [
{
"node": "./helpers/wrapRegExp.js",
"import": "./helpers/esm/wrapRegExp.js",
"default": "./helpers/wrapRegExp.js"
},
"./helpers/wrapRegExp.js"
],
"./helpers/esm/wrapRegExp": "./helpers/esm/wrapRegExp.js",
"./helpers/asyncIterator": [
{
"node": "./helpers/asyncIterator.js",
Expand Down Expand Up @@ -800,15 +809,6 @@
"./helpers/classPrivateMethodSet.js"
],
"./helpers/esm/classPrivateMethodSet": "./helpers/esm/classPrivateMethodSet.js",
"./helpers/wrapRegExp": [
{
"node": "./helpers/wrapRegExp.js",
"import": "./helpers/esm/wrapRegExp.js",
"default": "./helpers/wrapRegExp.js"
},
"./helpers/wrapRegExp.js"
],
"./helpers/esm/wrapRegExp": "./helpers/esm/wrapRegExp.js",
"./package": "./package.json",
"./package.json": "./package.json",
"./regenerator": "./regenerator/index.js",
Expand Down