Skip to content

Commit

Permalink
Add transform support for the "regexp unicode sets" proposal (#14125)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Jan 10, 2022
1 parent c8344f8 commit fe28bd8
Show file tree
Hide file tree
Showing 35 changed files with 368 additions and 85 deletions.
10 changes: 10 additions & 0 deletions packages/babel-core/src/parser/util/missing-plugin-helper.ts
Expand Up @@ -201,6 +201,16 @@ const pluginNameMap = {
url: "https://git.io/JvKp3",
},
},
regexpUnicodeSets: {
syntax: {
name: "@babel/plugin-syntax-unicode-sets-regex",
url: "https://git.io/J9GTd",
},
transform: {
name: "@babel/plugin-proposal-unicode-sets-regex",
url: "https://git.io/J9GTQ",
},
},
throwExpressions: {
syntax: {
name: "@babel/plugin-syntax-throw-expressions",
Expand Down
Expand Up @@ -19,7 +19,7 @@
],
"dependencies": {
"@babel/helper-annotate-as-pure": "workspace:^",
"regexpu-core": "^4.7.1"
"regexpu-core": "^5.0.1"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
Expand Down
Expand Up @@ -3,6 +3,8 @@ export const FEATURES = Object.freeze({
dotAllFlag: 1 << 1,
unicodePropertyEscape: 1 << 2,
namedCaptureGroups: 1 << 3,
unicodeSetsFlag_syntax: 1 << 4,
unicodeSetsFlag: 1 << 5,
});

// We can't use a symbol because this needs to always be the same, even if
Expand Down
48 changes: 15 additions & 33 deletions packages/babel-helper-create-regexp-features-plugin/src/index.ts
@@ -1,29 +1,10 @@
import rewritePattern from "regexpu-core";
import {
featuresKey,
FEATURES,
enableFeature,
runtimeKey,
hasFeature,
} from "./features";
import { generateRegexpuOptions } from "./util";
import { featuresKey, FEATURES, enableFeature, runtimeKey } from "./features";
import { generateRegexpuOptions, canSkipRegexpu, transformFlags } from "./util";

import { types as t } from "@babel/core";
import annotateAsPure from "@babel/helper-annotate-as-pure";

type RegExpFlags = "i" | "g" | "m" | "s" | "u" | "y";

/**
* Remove given flag from given RegExpLiteral node
*
* @param {RegExpLiteral} node
* @param {RegExpFlags} flag
* @returns {void}
*/
function pullFlag(node, flag: RegExpFlags): void {
node.flags = node.flags.replace(flag, "");
}

declare const PACKAGE_JSON: { name: string; version: string };

// Note: Versions are represented as an integer. e.g. 7.1.5 is represented
Expand All @@ -39,9 +20,13 @@ export function createRegExpFeaturePlugin({
name,
feature,
options = {} as any,
manipulateOptions = (() => {}) as (opts: any, parserOpts: any) => void,
}) {
return {
name,

manipulateOptions,

pre() {
const { file } = this;
const features = file.get(featuresKey) ?? 0;
Expand Down Expand Up @@ -70,20 +55,21 @@ export function createRegExpFeaturePlugin({
const { file } = this;
const features = file.get(featuresKey);
const runtime = file.get(runtimeKey) ?? true;
const regexpuOptions = generateRegexpuOptions(node, features);
if (regexpuOptions === null) {
return;
}

const regexpuOptions = generateRegexpuOptions(features);
if (canSkipRegexpu(node, regexpuOptions)) return;

const namedCaptureGroups = {};
if (regexpuOptions.namedGroup) {
if (regexpuOptions.namedGroups === "transform") {
regexpuOptions.onNamedGroup = (name, index) => {
namedCaptureGroups[name] = index;
};
}

node.pattern = rewritePattern(node.pattern, node.flags, regexpuOptions);

if (
regexpuOptions.namedGroup &&
regexpuOptions.namedGroups === "transform" &&
Object.keys(namedCaptureGroups).length > 0 &&
runtime &&
!isRegExpTest(path)
Expand All @@ -96,12 +82,8 @@ export function createRegExpFeaturePlugin({

path.replaceWith(call);
}
if (hasFeature(features, FEATURES.unicodeFlag)) {
pullFlag(node, "u");
}
if (hasFeature(features, FEATURES.dotAllFlag)) {
pullFlag(node, "s");
}

node.flags = transformFlags(regexpuOptions, node.flags);
},
},
};
Expand Down
104 changes: 58 additions & 46 deletions packages/babel-helper-create-regexp-features-plugin/src/util.ts
@@ -1,65 +1,77 @@
import type { types as t } from "@babel/core";
import { FEATURES, hasFeature } from "./features";

type RegexpuOptions = {
useUnicodeFlag: boolean;
unicodeFlag: "transform" | false;
unicodeSetsFlag: "transform" | "parse" | false;
dotAllFlag: "transform" | false;
unicodePropertyEscapes: "transform" | false;
namedGroups: "transform" | false;
onNamedGroup: (name: string, index: number) => void;
namedGroup: boolean;
unicodePropertyEscape: boolean;
dotAllFlag: boolean;
lookbehind: boolean;
};

export function generateRegexpuOptions(node, features): RegexpuOptions | null {
let useUnicodeFlag = false,
dotAllFlag = false,
unicodePropertyEscape = false,
namedGroup = false;
export function generateRegexpuOptions(toTransform: number): RegexpuOptions {
type Experimental = 1;

const feat = <Stability extends 0 | 1 = 0>(
name: keyof typeof FEATURES,
ok: "transform" | (Stability extends 0 ? never : "parse") = "transform",
) => {
return hasFeature(toTransform, FEATURES[name]) ? ok : false;
};

return {
unicodeFlag: feat("unicodeFlag"),
unicodeSetsFlag:
feat<Experimental>("unicodeSetsFlag") ||
feat<Experimental>("unicodeSetsFlag_syntax", "parse"),
dotAllFlag: feat("dotAllFlag"),
unicodePropertyEscapes: feat("unicodePropertyEscape"),
namedGroups: feat("namedCaptureGroups"),
onNamedGroup: () => {},
};
}

export function canSkipRegexpu(
node: t.RegExpLiteral,
options: RegexpuOptions,
): boolean {
const { flags, pattern } = node;
const flagsIncludesU = flags.includes("u");

if (flagsIncludesU) {
if (!hasFeature(features, FEATURES.unicodeFlag)) {
useUnicodeFlag = true;
}
if (flags.includes("v")) {
if (options.unicodeSetsFlag === "transform") return false;
}

if (flags.includes("u")) {
if (options.unicodeFlag === "transform") return false;
if (
hasFeature(features, FEATURES.unicodePropertyEscape) &&
options.unicodePropertyEscapes === "transform" &&
/\\[pP]{/.test(pattern)
) {
unicodePropertyEscape = true;
return false;
}
}

if (hasFeature(features, FEATURES.dotAllFlag) && flags.indexOf("s") >= 0) {
dotAllFlag = true;
if (flags.includes("s")) {
if (options.dotAllFlag === "transform") return false;
}
if (
hasFeature(features, FEATURES.namedCaptureGroups) &&
/\(\?<(?![=!])/.test(pattern)
) {
namedGroup = true;

if (options.namedGroups === "transform" && /\(\?<(?![=!])/.test(pattern)) {
return false;
}
if (
!namedGroup &&
!unicodePropertyEscape &&
!dotAllFlag &&
(!flagsIncludesU || useUnicodeFlag)
) {
return null;

return true;
}

export function transformFlags(regexpuOptions: RegexpuOptions, flags: string) {
if (regexpuOptions.unicodeSetsFlag === "transform") {
flags = flags.replace("v", "u");
}
// 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;
if (regexpuOptions.unicodeFlag === "transform") {
flags = flags.replace("u", "");
}
return {
useUnicodeFlag,
onNamedGroup: () => {},
namedGroup,
unicodePropertyEscape,
dotAllFlag,
lookbehind: true,
};
if (regexpuOptions.dotAllFlag === "transform") {
flags = flags.replace("s", "");
}
return flags;
}
@@ -1 +1 @@
/([0-9]{4})/;
/(\d{4})/;
3 changes: 3 additions & 0 deletions packages/babel-plugin-proposal-unicode-sets-regex/.npmignore
@@ -0,0 +1,3 @@
src
test
*.log
19 changes: 19 additions & 0 deletions packages/babel-plugin-proposal-unicode-sets-regex/README.md
@@ -0,0 +1,19 @@
# @babel/plugin-proposal-unicode-sets-regex

> Compile regular expressions' unicodeSets (v) flag.
See our website [@babel/plugin-proposal-unicode-sets-regex](https://babeljs.io/docs/en/babel-plugin-proposal-unicode-sets-regex) for more information.

## Install

Using npm:

```sh
npm install --save-dev @babel/plugin-proposal-unicode-sets-regex
```

or using yarn:

```sh
yarn add @babel/plugin-proposal-unicode-sets-regex --dev
```
48 changes: 48 additions & 0 deletions packages/babel-plugin-proposal-unicode-sets-regex/package.json
@@ -0,0 +1,48 @@
{
"name": "@babel/plugin-proposal-unicode-sets-regex",
"version": "7.16.7",
"description": "Compile regular expressions' unicodeSets (v) flag.",
"homepage": "https://babel.dev/docs/en/next/babel-plugin-proposal-unicode-sets-regex",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./lib/index.js",
"keywords": [
"babel-plugin",
"regex",
"regexp",
"unicode",
"sets",
"properties",
"property",
"string",
"strings",
"regular expressions"
],
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-plugin-proposal-unicode-sets-regex"
},
"bugs": "https://github.com/babel/babel/issues",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "workspace:^",
"@babel/helper-plugin-utils": "workspace:^"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
},
"devDependencies": {
"@babel/core": "workspace:^",
"@babel/helper-plugin-test-runner": "workspace:^"
},
"author": "The Babel Team (https://babel.dev/team)",
"exports": {
".": "./lib/index.js",
"./package.json": "./package.json"
},
"engines": {
"node": ">=6.9.0"
}
}
15 changes: 15 additions & 0 deletions packages/babel-plugin-proposal-unicode-sets-regex/src/index.ts
@@ -0,0 +1,15 @@
/* eslint-disable @babel/development/plugin-name */
import { createRegExpFeaturePlugin } from "@babel/helper-create-regexp-features-plugin";
import { declare } from "@babel/helper-plugin-utils";

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

return createRegExpFeaturePlugin({
name: "transform-unicode-sets-regex",
feature: "unicodeSetsFlag",
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("regexpUnicodeSets");
},
});
});
@@ -0,0 +1 @@
/[[0-7]&&[5-9]]\u{10FFFF}/v;
@@ -0,0 +1,3 @@
{
"plugins": ["proposal-unicode-sets-regex"]
}
@@ -0,0 +1 @@
/[5-7]\u{10FFFF}/u;
@@ -0,0 +1 @@
/[[0-7]&&[5-9]]\u{10FFFF}/v;
@@ -0,0 +1,3 @@
{
"plugins": ["proposal-unicode-sets-regex", "transform-unicode-regex"]
}
@@ -0,0 +1 @@
/[5-7](?:\uDBFF\uDFFF)/;
@@ -0,0 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";

runner(import.meta.url);
@@ -0,0 +1 @@
{ "type": "module" }
3 changes: 3 additions & 0 deletions packages/babel-plugin-syntax-unicode-sets-regex/.npmignore
@@ -0,0 +1,3 @@
src
test
*.log
19 changes: 19 additions & 0 deletions packages/babel-plugin-syntax-unicode-sets-regex/README.md
@@ -0,0 +1,19 @@
# @babel/plugin-syntax-unicode-sets-regex

> Parse regular expressions' unicodeSets (v) flag.
See our website [@babel/plugin-syntax-unicode-sets-regex](https://babeljs.io/docs/en/babel-plugin-syntax-unicode-sets-regex) for more information.

## Install

Using npm:

```sh
npm install --save-dev @babel/plugin-syntax-unicode-sets-regex
```

or using yarn:

```sh
yarn add @babel/plugin-syntax-unicode-sets-regex --dev
```

0 comments on commit fe28bd8

Please sign in to comment.