Skip to content

Commit 10fe2f3

Browse files
Add transform support for the "regexp unicode sets" proposal (#14125)
1 parent 96a8251 commit 10fe2f3

35 files changed

Lines changed: 368 additions & 85 deletions

File tree

packages/babel-core/src/parser/util/missing-plugin-helper.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,16 @@ const pluginNameMap = {
201201
url: "https://git.io/JvKp3",
202202
},
203203
},
204+
regexpUnicodeSets: {
205+
syntax: {
206+
name: "@babel/plugin-syntax-unicode-sets-regex",
207+
url: "https://git.io/J9GTd",
208+
},
209+
transform: {
210+
name: "@babel/plugin-proposal-unicode-sets-regex",
211+
url: "https://git.io/J9GTQ",
212+
},
213+
},
204214
throwExpressions: {
205215
syntax: {
206216
name: "@babel/plugin-syntax-throw-expressions",

packages/babel-helper-create-regexp-features-plugin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
],
2020
"dependencies": {
2121
"@babel/helper-annotate-as-pure": "workspace:^",
22-
"regexpu-core": "^4.7.1"
22+
"regexpu-core": "^5.0.1"
2323
},
2424
"peerDependencies": {
2525
"@babel/core": "^7.0.0"

packages/babel-helper-create-regexp-features-plugin/src/features.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ export const FEATURES = Object.freeze({
33
dotAllFlag: 1 << 1,
44
unicodePropertyEscape: 1 << 2,
55
namedCaptureGroups: 1 << 3,
6+
unicodeSetsFlag_syntax: 1 << 4,
7+
unicodeSetsFlag: 1 << 5,
68
});
79

810
// We can't use a symbol because this needs to always be the same, even if

packages/babel-helper-create-regexp-features-plugin/src/index.ts

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,10 @@
11
import rewritePattern from "regexpu-core";
2-
import {
3-
featuresKey,
4-
FEATURES,
5-
enableFeature,
6-
runtimeKey,
7-
hasFeature,
8-
} from "./features";
9-
import { generateRegexpuOptions } from "./util";
2+
import { featuresKey, FEATURES, enableFeature, runtimeKey } from "./features";
3+
import { generateRegexpuOptions, canSkipRegexpu, transformFlags } from "./util";
104

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

14-
type RegExpFlags = "i" | "g" | "m" | "s" | "u" | "y";
15-
16-
/**
17-
* Remove given flag from given RegExpLiteral node
18-
*
19-
* @param {RegExpLiteral} node
20-
* @param {RegExpFlags} flag
21-
* @returns {void}
22-
*/
23-
function pullFlag(node, flag: RegExpFlags): void {
24-
node.flags = node.flags.replace(flag, "");
25-
}
26-
278
declare const PACKAGE_JSON: { name: string; version: string };
289

2910
// Note: Versions are represented as an integer. e.g. 7.1.5 is represented
@@ -39,9 +20,13 @@ export function createRegExpFeaturePlugin({
3920
name,
4021
feature,
4122
options = {} as any,
23+
manipulateOptions = (() => {}) as (opts: any, parserOpts: any) => void,
4224
}) {
4325
return {
4426
name,
27+
28+
manipulateOptions,
29+
4530
pre() {
4631
const { file } = this;
4732
const features = file.get(featuresKey) ?? 0;
@@ -70,20 +55,21 @@ export function createRegExpFeaturePlugin({
7055
const { file } = this;
7156
const features = file.get(featuresKey);
7257
const runtime = file.get(runtimeKey) ?? true;
73-
const regexpuOptions = generateRegexpuOptions(node, features);
74-
if (regexpuOptions === null) {
75-
return;
76-
}
58+
59+
const regexpuOptions = generateRegexpuOptions(features);
60+
if (canSkipRegexpu(node, regexpuOptions)) return;
61+
7762
const namedCaptureGroups = {};
78-
if (regexpuOptions.namedGroup) {
63+
if (regexpuOptions.namedGroups === "transform") {
7964
regexpuOptions.onNamedGroup = (name, index) => {
8065
namedCaptureGroups[name] = index;
8166
};
8267
}
68+
8369
node.pattern = rewritePattern(node.pattern, node.flags, regexpuOptions);
8470

8571
if (
86-
regexpuOptions.namedGroup &&
72+
regexpuOptions.namedGroups === "transform" &&
8773
Object.keys(namedCaptureGroups).length > 0 &&
8874
runtime &&
8975
!isRegExpTest(path)
@@ -96,12 +82,8 @@ export function createRegExpFeaturePlugin({
9682

9783
path.replaceWith(call);
9884
}
99-
if (hasFeature(features, FEATURES.unicodeFlag)) {
100-
pullFlag(node, "u");
101-
}
102-
if (hasFeature(features, FEATURES.dotAllFlag)) {
103-
pullFlag(node, "s");
104-
}
85+
86+
node.flags = transformFlags(regexpuOptions, node.flags);
10587
},
10688
},
10789
};
Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,77 @@
1+
import type { types as t } from "@babel/core";
12
import { FEATURES, hasFeature } from "./features";
23

34
type RegexpuOptions = {
4-
useUnicodeFlag: boolean;
5+
unicodeFlag: "transform" | false;
6+
unicodeSetsFlag: "transform" | "parse" | false;
7+
dotAllFlag: "transform" | false;
8+
unicodePropertyEscapes: "transform" | false;
9+
namedGroups: "transform" | false;
510
onNamedGroup: (name: string, index: number) => void;
6-
namedGroup: boolean;
7-
unicodePropertyEscape: boolean;
8-
dotAllFlag: boolean;
9-
lookbehind: boolean;
1011
};
1112

12-
export function generateRegexpuOptions(node, features): RegexpuOptions | null {
13-
let useUnicodeFlag = false,
14-
dotAllFlag = false,
15-
unicodePropertyEscape = false,
16-
namedGroup = false;
13+
export function generateRegexpuOptions(toTransform: number): RegexpuOptions {
14+
type Experimental = 1;
15+
16+
const feat = <Stability extends 0 | 1 = 0>(
17+
name: keyof typeof FEATURES,
18+
ok: "transform" | (Stability extends 0 ? never : "parse") = "transform",
19+
) => {
20+
return hasFeature(toTransform, FEATURES[name]) ? ok : false;
21+
};
22+
23+
return {
24+
unicodeFlag: feat("unicodeFlag"),
25+
unicodeSetsFlag:
26+
feat<Experimental>("unicodeSetsFlag") ||
27+
feat<Experimental>("unicodeSetsFlag_syntax", "parse"),
28+
dotAllFlag: feat("dotAllFlag"),
29+
unicodePropertyEscapes: feat("unicodePropertyEscape"),
30+
namedGroups: feat("namedCaptureGroups"),
31+
onNamedGroup: () => {},
32+
};
33+
}
34+
35+
export function canSkipRegexpu(
36+
node: t.RegExpLiteral,
37+
options: RegexpuOptions,
38+
): boolean {
1739
const { flags, pattern } = node;
18-
const flagsIncludesU = flags.includes("u");
1940

20-
if (flagsIncludesU) {
21-
if (!hasFeature(features, FEATURES.unicodeFlag)) {
22-
useUnicodeFlag = true;
23-
}
41+
if (flags.includes("v")) {
42+
if (options.unicodeSetsFlag === "transform") return false;
43+
}
44+
45+
if (flags.includes("u")) {
46+
if (options.unicodeFlag === "transform") return false;
2447
if (
25-
hasFeature(features, FEATURES.unicodePropertyEscape) &&
48+
options.unicodePropertyEscapes === "transform" &&
2649
/\\[pP]{/.test(pattern)
2750
) {
28-
unicodePropertyEscape = true;
51+
return false;
2952
}
3053
}
3154

32-
if (hasFeature(features, FEATURES.dotAllFlag) && flags.indexOf("s") >= 0) {
33-
dotAllFlag = true;
55+
if (flags.includes("s")) {
56+
if (options.dotAllFlag === "transform") return false;
3457
}
35-
if (
36-
hasFeature(features, FEATURES.namedCaptureGroups) &&
37-
/\(\?<(?![=!])/.test(pattern)
38-
) {
39-
namedGroup = true;
58+
59+
if (options.namedGroups === "transform" && /\(\?<(?![=!])/.test(pattern)) {
60+
return false;
4061
}
41-
if (
42-
!namedGroup &&
43-
!unicodePropertyEscape &&
44-
!dotAllFlag &&
45-
(!flagsIncludesU || useUnicodeFlag)
46-
) {
47-
return null;
62+
63+
return true;
64+
}
65+
66+
export function transformFlags(regexpuOptions: RegexpuOptions, flags: string) {
67+
if (regexpuOptions.unicodeSetsFlag === "transform") {
68+
flags = flags.replace("v", "u");
4869
}
49-
// Now we have to feed regexpu-core the regex
50-
if (flagsIncludesU && flags.indexOf("s") >= 0) {
51-
// When flags includes u, `config.unicode` will be enabled even if `u` is supported natively.
52-
// In this case we have to enable dotAllFlag, otherwise `rewritePattern(/./su)` will return
53-
// incorrect result
54-
// https://github.com/mathiasbynens/regexpu-core/blob/v4.6.0/rewrite-pattern.js#L191
55-
dotAllFlag = true;
70+
if (regexpuOptions.unicodeFlag === "transform") {
71+
flags = flags.replace("u", "");
5672
}
57-
return {
58-
useUnicodeFlag,
59-
onNamedGroup: () => {},
60-
namedGroup,
61-
unicodePropertyEscape,
62-
dotAllFlag,
63-
lookbehind: true,
64-
};
73+
if (regexpuOptions.dotAllFlag === "transform") {
74+
flags = flags.replace("s", "");
75+
}
76+
return flags;
6577
}
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
/([0-9]{4})/;
1+
/(\d{4})/;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
src
2+
test
3+
*.log
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# @babel/plugin-proposal-unicode-sets-regex
2+
3+
> Compile regular expressions' unicodeSets (v) flag.
4+
5+
See our website [@babel/plugin-proposal-unicode-sets-regex](https://babeljs.io/docs/en/babel-plugin-proposal-unicode-sets-regex) for more information.
6+
7+
## Install
8+
9+
Using npm:
10+
11+
```sh
12+
npm install --save-dev @babel/plugin-proposal-unicode-sets-regex
13+
```
14+
15+
or using yarn:
16+
17+
```sh
18+
yarn add @babel/plugin-proposal-unicode-sets-regex --dev
19+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "@babel/plugin-proposal-unicode-sets-regex",
3+
"version": "7.16.7",
4+
"description": "Compile regular expressions' unicodeSets (v) flag.",
5+
"homepage": "https://babel.dev/docs/en/next/babel-plugin-proposal-unicode-sets-regex",
6+
"license": "MIT",
7+
"publishConfig": {
8+
"access": "public"
9+
},
10+
"main": "./lib/index.js",
11+
"keywords": [
12+
"babel-plugin",
13+
"regex",
14+
"regexp",
15+
"unicode",
16+
"sets",
17+
"properties",
18+
"property",
19+
"string",
20+
"strings",
21+
"regular expressions"
22+
],
23+
"repository": {
24+
"type": "git",
25+
"url": "https://github.com/babel/babel.git",
26+
"directory": "packages/babel-plugin-proposal-unicode-sets-regex"
27+
},
28+
"bugs": "https://github.com/babel/babel/issues",
29+
"dependencies": {
30+
"@babel/helper-create-regexp-features-plugin": "workspace:^",
31+
"@babel/helper-plugin-utils": "workspace:^"
32+
},
33+
"peerDependencies": {
34+
"@babel/core": "^7.0.0"
35+
},
36+
"devDependencies": {
37+
"@babel/core": "workspace:^",
38+
"@babel/helper-plugin-test-runner": "workspace:^"
39+
},
40+
"author": "The Babel Team (https://babel.dev/team)",
41+
"exports": {
42+
".": "./lib/index.js",
43+
"./package.json": "./package.json"
44+
},
45+
"engines": {
46+
"node": ">=6.9.0"
47+
}
48+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* eslint-disable @babel/development/plugin-name */
2+
import { createRegExpFeaturePlugin } from "@babel/helper-create-regexp-features-plugin";
3+
import { declare } from "@babel/helper-plugin-utils";
4+
5+
export default declare(api => {
6+
api.assertVersion(7);
7+
8+
return createRegExpFeaturePlugin({
9+
name: "transform-unicode-sets-regex",
10+
feature: "unicodeSetsFlag",
11+
manipulateOptions(opts, parserOpts) {
12+
parserOpts.plugins.push("regexpUnicodeSets");
13+
},
14+
});
15+
});

0 commit comments

Comments
 (0)