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

Add transform support for the "regexp unicode sets" proposal #14125

Merged
Show file tree
Hide file tree
Changes from all 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
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
```