Add @babel/plugin-transform-named-capturing-groups-regex (#7105)

When the `runtime` flag is on (by default), this plugin adds a new helper which wraps the native `RegExp` class to provide groups support. People nees to use a polyfill (I implemented it in core-js) for browsers that don't support ES6 regexps.
This commit is contained in:
Nicolò Ribaudo 2019-01-15 18:21:17 +01:00 committed by GitHub
parent aaec2cd51d
commit a27b9b4299
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 262 additions and 0 deletions

View File

@ -1783,3 +1783,75 @@ helpers.classPrivateMethodSet = helper("7.1.6")`
throw new TypeError("attempted to reassign private method"); throw new TypeError("attempted to reassign private method");
} }
`; `;
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, groups);
};
var _RegExp = wrapNativeSuper(RegExp);
var _super = RegExp.prototype;
var _groups = new WeakMap();
function BabelRegExp(re, groups) {
var _this = _RegExp.call(this, re);
_groups.set(_this, groups);
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(groups).reduce(function(groups, name) {
groups[name] = result[g[name]];
return groups;
}, Object.create(null));
}
return _wrapRegExp.apply(this, arguments);
}
`;

View File

@ -0,0 +1,3 @@
src
test
*.log

View File

@ -0,0 +1,19 @@
# @babel/plugin-transform-named-capturing-groups-regex
> Compile regular expressions using named groups to ES5.
See our website [@babel/plugin-transform-named-capturing-groups-regex](https://babeljs.io/docs/en/next/babel-plugin-transform-named-capturing-groups-regex.html) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/plugin-transform-named-capturing-groups-regex
```
or using yarn:
```sh
yarn add @babel/plugin-transform-named-capturing-groups-regex --dev
```

View File

@ -0,0 +1,26 @@
{
"name": "@babel/plugin-transform-named-capturing-groups-regex",
"version": "7.2.5",
"description": "Compile regular expressions using named groups to ES5.",
"homepage": "https://babeljs.io/",
"license": "MIT",
"main": "lib/index.js",
"keywords": [
"babel-plugin",
"regex",
"regexp",
"regular expressions"
],
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-named-capturing-groups-regex",
"bugs": "https://github.com/babel/babel/issues",
"dependencies": {
"regexp-tree": "^0.1.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/helper-plugin-test-runner": "^7.0.0"
}
}

View File

@ -0,0 +1,54 @@
import regexpTree from "regexp-tree";
export default function({ types: t }, options) {
const { runtime = true } = options;
if (typeof runtime !== "boolean") {
throw new Error("The 'runtime' option must be boolean");
}
return {
name: "transform-named-capturing-groups-regex",
visitor: {
RegExpLiteral(path) {
const node = path.node;
if (node.pattern.indexOf("(?<") === -1) {
// Return early if there are no named groups.
// The .indexOf check may have false positives (e.g. /\(?</); in
// this case we parse the regex and regexp-tree won't transform it.
return;
}
const result = regexpTree.compatTranspile(node.extra.raw, [
"namedCapturingGroups",
]);
const { namedCapturingGroups } = result.getExtra();
if (
namedCapturingGroups &&
Object.keys(namedCapturingGroups).length > 0
) {
node.pattern = result.getSource();
if (runtime && !isRegExpTest(path)) {
path.replaceWith(
t.callExpression(this.addHelper("wrapRegExp"), [
node,
t.valueToNode(namedCapturingGroups),
]),
);
}
}
},
},
};
}
function isRegExpTest(path) {
return (
path.parentPath.isMemberExpression({
object: path.node,
computed: false,
}) && path.parentPath.get("property").isIdentifier({ name: "test" })
);
}

View File

@ -0,0 +1,15 @@
var re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
var result = re.exec("2017-12-23");
expect(result.groups).toEqual({
year: "2017",
month: "12",
day: "23",
});
expect(result.groups).toEqual({
year: result[1],
month: result[2],
day: result[3],
});

View File

@ -0,0 +1,3 @@
var re = /(?<group>)/;
expect(re instanceof RegExp).toBeTruthy();

View File

@ -0,0 +1,15 @@
var re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
var result = "1999-09-29".match(re);
expect(result.groups).toEqual({
year: "1999",
month: "09",
day: "29",
});
expect(result.groups).toEqual({
year: result[1],
month: result[2],
day: result[3],
});

View File

@ -0,0 +1,4 @@
var re = /no-groups-\(?<looks-like-a-group>looks\)/;
var result = re.exec("no-groups-(<looks-like-a-group>looks)")
expect(result.groups).toBeUndefined();

View File

@ -0,0 +1,3 @@
{
"plugins": ["transform-named-capturing-groups-regex"]
}

View File

@ -0,0 +1,5 @@
var re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
var result = "2015-10-31".replace(re, "$<day>/$<month>/$<year>")
expect(result).toBe("31/10/2015");

View File

@ -0,0 +1 @@
/(?<name>)\k<name>/;

View File

@ -0,0 +1,6 @@
{
"plugins": [
"external-helpers",
["transform-named-capturing-groups-regex", { "runtime": false }]
]
}

View File

@ -0,0 +1,3 @@
{
"throws": "invalid group Unicode name \"\\u{41}\", use `u` flag."
}

View File

@ -0,0 +1 @@
/(?<\u{41}>)\k<\u{41}>/u;

View File

@ -0,0 +1,6 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.1000.0" }],
["transform-named-capturing-groups-regex", { "runtime": false }]
]
}

View File

@ -0,0 +1 @@
"foo".match(/(?<double>.)\k<double>/);

View File

@ -0,0 +1,3 @@
"foo".match(babelHelpers.wrapRegExp(/(.)\1/, {
double: 1
}));

View File

@ -0,0 +1 @@
/no-groups-\(?<looks-like-a-group>looks\)/;

View File

@ -0,0 +1 @@
/no-groups-\(?<looks-like-a-group>looks\)/;

View File

@ -0,0 +1,6 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.1000.0" }],
"transform-named-capturing-groups-regex"
]
}

View File

@ -0,0 +1 @@
"abba".match(/(.)(?<n>.)\k<n>\1/);

View File

@ -0,0 +1,3 @@
"abba".match(babelHelpers.wrapRegExp(/(.)(.)\2\1/, {
n: 2
}));

View File

@ -0,0 +1 @@
/^(?<x>.)\k<x>$/.test("aa");

View File

@ -0,0 +1 @@
/^(.)\1$/.test("aa");

View File

@ -0,0 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";
runner(__dirname);