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

Merge multiple regex transform plugin #10447

Merged
@@ -0,0 +1,3 @@
src
test
*.log
19 changes: 19 additions & 0 deletions packages/babel-helper-create-regexp-features-plugin/README.md
@@ -0,0 +1,19 @@
# @babel/helper-create-regexp-features-plugin

> Compile ESNext Regular Expressions to ES5

See our website [@babel/helper-create-regexp-features-plugin](https://babeljs.io/docs/en/next/babel-helper-create-regexp-features-plugin.html) for more information.
Copy link
Contributor

Choose a reason for hiding this comment

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

This link is dead. Is there supposed to be anything here?


## Install

Using npm:

```sh
npm install --save-dev @babel/helper-create-regexp-features-plugin
```

or using yarn:

```sh
yarn add @babel/helper-create-regexp-features-plugin --dev
```
31 changes: 31 additions & 0 deletions packages/babel-helper-create-regexp-features-plugin/package.json
@@ -0,0 +1,31 @@
{
"name": "@babel/helper-create-regexp-features-plugin",
"version": "7.6.0",
"author": "The Babel Team (https://babeljs.io/team)",
"license": "MIT",
"description": "Compile ESNext Regular Expressions to ES5",
"repository": {
"type": "git",
"repository": "https://github.com/babel/babel",
"directory": "packages/babel-helper-create-regexp-features-plugin"
},
"main": "lib/index.js",
"publishConfig": {
"access": "public"
},
"keywords": [
"babel",
"babel-plugin"
],
"dependencies": {
"@babel/helper-regex": "^7.4.4",
"regexpu-core": "^4.6.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
},
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/helper-plugin-test-runner": "^7.0.0"
}
}
@@ -0,0 +1,27 @@
// @flow
export const FEATURES = Object.freeze({
unicodeFlag: 1 << 0,
dotAllFlag: 1 << 1,
unicodePropertyEscape: 1 << 2,
namedCaptureGroups: 1 << 3,
});

// We can't use a symbol because this needs to always be the same, even if
// this package isn't deduped by npm. e.g.
// - node_modules/
// - @babel/plugin-regexp-features
// - @babel/plugin-proposal-unicode-property-regex
// - node_modules
// - @babel-plugin-regexp-features
export const featuresKey = "@babel/plugin-regexp-features/featuresKey";
export const runtimeKey = "@babel/plugin-regexp-features/runtimeKey";

type FeatureType = $Values<typeof FEATURES>;

export function enableFeature(features: number, feature: FeatureType): number {
return features | feature;
}

export function hasFeature(features: number, feature: FeatureType) {
return !!(features & feature);
}
96 changes: 96 additions & 0 deletions packages/babel-helper-create-regexp-features-plugin/src/index.js
@@ -0,0 +1,96 @@
import rewritePattern from "regexpu-core";
import {
featuresKey,
FEATURES,
enableFeature,
runtimeKey,
hasFeature,
} from "./features";
import { generateRegexpuOptions } from "./util";

import pkg from "../package.json";
import { types as t } from "@babel/core";
import { pullFlag } from "@babel/helper-regex";

// Note: Versions are represented as an integer. e.g. 7.1.5 is represented
// as 70000100005. This method is easier than using a semver-parsing
// package, but it breaks if we release x.y.z where x, y or z are
// greater than 99_999.
const version = pkg.version.split(".").reduce((v, x) => v * 1e5 + +x, 0);
Copy link
Member

Choose a reason for hiding this comment

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

FWIW, I think it's perfectly fine using semver to parse (since we use it elsewhere)

Copy link
Member

Choose a reason for hiding this comment

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

It's my fault 😛 I used this logic for class features

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it is okay to leave it as is because all we need is a version sorter and we are not using semver elsewhere.

const versionKey = "@babel/plugin-regexp-features/version";

export function createRegExpFeaturePlugin({ name, feature, options = {} }) {
return {
name,
pre() {
const { file } = this;
const features = file.get(featuresKey) ?? 0;
let newFeatures = enableFeature(features, FEATURES[feature]);

const { useUnicodeFlag, runtime = true } = options;
if (useUnicodeFlag === false) {
newFeatures = enableFeature(newFeatures, FEATURES.unicodeFlag);
}
if (newFeatures !== features) {
file.set(featuresKey, newFeatures);
}

if (!runtime) {
file.set(runtimeKey, false);
}

if (!file.has(versionKey) || file.get(versionKey) < version) {
file.set(versionKey, version);
}
},

visitor: {
RegExpLiteral(path) {
const { node } = path;
const { file } = this;
const features = file.get(featuresKey);
const runtime = file.get(runtimeKey) ?? true;
const regexpuOptions = generateRegexpuOptions(node, features);
if (regexpuOptions === null) {
return;
}
const namedCaptureGroups = {};
if (regexpuOptions.namedGroup) {
regexpuOptions.onNamedGroup = (name, index) => {
namedCaptureGroups[name] = index;
};
}
node.pattern = rewritePattern(node.pattern, node.flags, regexpuOptions);

if (
regexpuOptions.namedGroup &&
Object.keys(namedCaptureGroups).length > 0 &&
runtime &&
!isRegExpTest(path)
) {
path.replaceWith(
t.callExpression(this.addHelper("wrapRegExp"), [
node,
t.valueToNode(namedCaptureGroups),
]),
);
}
if (hasFeature(features, FEATURES.unicodeFlag)) {
pullFlag(node, "u");
}
if (hasFeature(features, FEATURES.dotAllFlag)) {
pullFlag(node, "s");
}
},
},
};
}

function isRegExpTest(path) {
return (
path.parentPath.isMemberExpression({
object: path.node,
computed: false,
}) && path.parentPath.get("property").isIdentifier({ name: "test" })
);
}
56 changes: 56 additions & 0 deletions packages/babel-helper-create-regexp-features-plugin/src/util.js
@@ -0,0 +1,56 @@
import { FEATURES, hasFeature } from "./features";

export function generateRegexpuOptions(node, features) {
let useUnicodeFlag = false,
dotAllFlag = false,
unicodePropertyEscape = false,
namedGroup = false;
const { flags, pattern } = node;
const flagsIncludesU = flags.includes("u");

if (flagsIncludesU) {
if (!hasFeature(features, FEATURES.unicodeFlag)) {
useUnicodeFlag = true;
}
if (
hasFeature(features, FEATURES.unicodePropertyEscape) &&
/\\[pP]{/.test(pattern)
) {
unicodePropertyEscape = true;
}
}

if (hasFeature(features, FEATURES.dotAllFlag) && flags.indexOf("s") >= 0) {
dotAllFlag = true;
}
if (
hasFeature(features, FEATURES.namedCaptureGroups) &&
/\(\?<(?![=!])/.test(pattern)
) {
namedGroup = true;
}
if (
!namedGroup &&
!unicodePropertyEscape &&
!dotAllFlag &&
(!flagsIncludesU || useUnicodeFlag)
) {
return null;
}
// Now we have to feed regexpu-core the regex
if (flagsIncludesU && flags.indexOf("s") >= 0) {
// When flags includes u, `config.unicode` will be enabled even if `u` is supported natively.
// In this case we have to enable dotAllFlag, otherwise `rewritePattern(/./su)` will return
// incorrect result
// https://github.com/mathiasbynens/regexpu-core/blob/v4.6.0/rewrite-pattern.js#L191
dotAllFlag = true;
}
return {
useUnicodeFlag,
onNamedGroup: () => {},
namedGroup,
unicodePropertyEscape,
dotAllFlag,
lookbehind: true,
};
}
@@ -0,0 +1 @@
/\p{Script_Extensions=Wancho}/u
@@ -0,0 +1,6 @@
{
"plugins": [
["proposal-unicode-property-regex", { "useUnicodeFlag": false }, "name 1"],
["proposal-unicode-property-regex", { "useUnicodeFlag": true }, "name 2"]
]
}
@@ -0,0 +1 @@
/(?:\uD838[\uDEC0-\uDEF9\uDEFF])/;
@@ -0,0 +1 @@
/(?<year>\d{4})/
@@ -0,0 +1,6 @@
{
"plugins": [
["transform-named-capturing-groups-regex", { "runtime": false }, "name 1"],
["transform-named-capturing-groups-regex", { "runtime": true }, "name 2"]
]
}
@@ -0,0 +1 @@
/([0-9]{4})/;
@@ -0,0 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";

runner(__dirname);
Expand Up @@ -22,9 +22,8 @@
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-unicode-property-regex",
"bugs": "https://github.com/babel/babel/issues",
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/helper-regex": "^7.4.4",
"regexpu-core": "^4.6.0"
"@babel/helper-create-regexp-features-plugin": "^7.6.0",
"@babel/helper-plugin-utils": "^7.0.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
Expand Down
26 changes: 6 additions & 20 deletions packages/babel-plugin-proposal-unicode-property-regex/src/index.js
@@ -1,6 +1,6 @@
/* eslint-disable @babel/development/plugin-name */
import { createRegExpFeaturePlugin } from "@babel/helper-create-regexp-features-plugin";
import { declare } from "@babel/helper-plugin-utils";
import rewritePattern from "regexpu-core";
import * as regex from "@babel/helper-regex";

export default declare((api, options) => {
api.assertVersion(7);
Expand All @@ -10,23 +10,9 @@ export default declare((api, options) => {
throw new Error(".useUnicodeFlag must be a boolean, or undefined");
}

return {
return createRegExpFeaturePlugin({
Copy link
Member

Choose a reason for hiding this comment

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

💯, clean!

name: "proposal-unicode-property-regex",

visitor: {
RegExpLiteral(path) {
const node = path.node;
if (!regex.is(node, "u")) {
return;
}
node.pattern = rewritePattern(node.pattern, node.flags, {
unicodePropertyEscape: true,
useUnicodeFlag,
});
if (!useUnicodeFlag) {
regex.pullFlag(node, "u");
}
},
},
};
feature: "unicodePropertyEscape",
options: { useUnicodeFlag },
});
});
@@ -1 +1 @@
var regex = /[\u{1E2C0}-\u{1E2F9}\u{1E2FF}]/u;
var regex = /[\p{Script_Extensions=Wancho}]/u;
8 changes: 2 additions & 6 deletions packages/babel-plugin-transform-dotall-regex/package.json
Expand Up @@ -8,9 +8,6 @@
"access": "public"
},
"main": "lib/index.js",
"engines": {
"node": ">=4"
},
"keywords": [
"babel-plugin",
"regex",
Expand All @@ -21,9 +18,8 @@
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-dotall-regex",
"bugs": "https://github.com/babel/babel/issues",
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/helper-regex": "^7.4.4",
"regexpu-core": "^4.6.0"
"@babel/helper-create-regexp-features-plugin": "^7.6.0",
"@babel/helper-plugin-utils": "^7.0.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
Expand Down
23 changes: 5 additions & 18 deletions packages/babel-plugin-transform-dotall-regex/src/index.js
@@ -1,25 +1,12 @@
/* eslint-disable @babel/development/plugin-name */
import { createRegExpFeaturePlugin } from "@babel/helper-create-regexp-features-plugin";
import { declare } from "@babel/helper-plugin-utils";
import rewritePattern from "regexpu-core";
import * as regex from "@babel/helper-regex";

export default declare(api => {
api.assertVersion(7);

return {
return createRegExpFeaturePlugin({
name: "transform-dotall-regex",

visitor: {
RegExpLiteral(path) {
const node = path.node;
if (!regex.is(node, "s")) {
return;
}
node.pattern = rewritePattern(node.pattern, node.flags, {
dotAllFlag: true,
useUnicodeFlag: regex.is(node, "u"),
});
regex.pullFlag(node, "s");
},
},
};
feature: "dotAllFlag",
});
});
@@ -0,0 +1,2 @@
var a = /\p{Unified_Ideograph}./u;
var b = /\p{Unified_Ideograph}./su;
@@ -0,0 +1,3 @@
{
"plugins": ["transform-dotall-regex", "proposal-unicode-property-regex"]
}

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

Expand Up @@ -21,7 +21,7 @@
},
"bugs": "https://github.com/babel/babel/issues",
"dependencies": {
"regexpu-core": "^4.6.0"
"@babel/helper-create-regexp-features-plugin": "^7.6.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
Expand Down