Skip to content

Commit

Permalink
Merge multiple regex transform plugin (#10447)
Browse files Browse the repository at this point in the history
* feat: implement create-regexp-features-plugin

* fix: test input is not effective

* refactor: leverage create-regexp-features-plugin

* test: add more test cases

* test: update test fixture

* chore: add type annotation to features

* test: add regression test for issue 9892

* add regression test for issue 9199

* address review comments from Nicolò

* address review comments from Brian

* small tweaks

* Enable dotAllFlag when flags includes u
  • Loading branch information
JLHwung authored and nicolo-ribaudo committed Oct 29, 2019
1 parent ec3345b commit 8ffca04
Show file tree
Hide file tree
Showing 38 changed files with 333 additions and 113 deletions.
@@ -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.

## 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);
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({
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

0 comments on commit 8ffca04

Please sign in to comment.