Skip to content

Commit 0e0f123

Browse files
authored
Fix: class transform should not drop method definition when key contains non-BMP characters (#14897)
* polish: improve helper-function-name typings * fix: support non-BMP method name in class transform * polish: use nullish coalescing * add Babel 8 test cases * update JSDoc annotations
1 parent 05deb60 commit 0e0f123

39 files changed

Lines changed: 266 additions & 19 deletions

File tree

packages/babel-helper-function-name/src/index.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,22 @@ function visit(
197197
}
198198

199199
/**
200-
* @param {NodePath} param0
201-
* @param {Boolean} localBinding whether a name could shadow a self-reference (e.g. converting arrow function)
202-
* @param {Boolean} supportUnicodeId whether a target support unicodeId or not
200+
* Add id to function/class expression inferred from the AST
201+
*
202+
* @export
203+
* @template N The unamed expression type
204+
* @param {Object} nodePathLike The NodePath-like input
205+
* @param {N} nodePathLike.node an AST node
206+
* @param {NodePath<N>["parent"]} [nodePathLike.parent] The parent of the AST node
207+
* @param {Scope} nodePathLike.scope The scope associated to the AST node
208+
* @param {t.LVal | t.StringLiteral | t.NumericLiteral | t.BigIntLiteral} [nodePathLike.id] the fallback naming source when the helper
209+
* can not infer the function name from the AST
210+
* @param {boolean} [localBinding=false] whether a name could shadow a self-reference (e.g. converting arrow function)
211+
* @param {boolean} [supportUnicodeId=false] whether the compilation target supports unicodeId (non-BMP characters) or not
212+
* @returns {(N | t.CallExpression | void)}
213+
* - modified node when name can be inferred,
214+
* - an IIFE when `node` contains a binding shadowing the inferred function name (e.g. `let f = function (f) {}`),
215+
* - `void` when `node` has `id` property or the helper can not inferred the name or the inferred name contains non-BMP characters that is not supported by current target
203216
*/
204217
export default function <N extends t.FunctionExpression | t.Class>(
205218
{
@@ -209,13 +222,13 @@ export default function <N extends t.FunctionExpression | t.Class>(
209222
id,
210223
}: {
211224
node: N;
212-
parent?: t.Node;
225+
parent?: NodePath<N>["parent"];
213226
scope: Scope;
214227
id?: t.LVal | t.StringLiteral | t.NumericLiteral | t.BigIntLiteral;
215228
},
216229
localBinding = false,
217230
supportUnicodeId = false,
218-
): t.CallExpression | N {
231+
): N | t.CallExpression | void {
219232
// has an `id` so we don't need to infer one
220233
if (node.id) return;
221234

packages/babel-helper-wrap-function/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ function plainFunction(
148148
const returnFn = container.callee.body.body[1].argument;
149149
nameFunction({
150150
node: returnFn,
151-
parent: path.parent,
151+
parent: (path as NodePath<t.FunctionExpression>).parent,
152152
scope: path.scope,
153153
});
154154
functionId = returnFn.id;

packages/babel-plugin-transform-classes/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"main": "./lib/index.js",
1616
"dependencies": {
1717
"@babel/helper-annotate-as-pure": "workspace:^",
18+
"@babel/helper-compilation-targets": "workspace:^",
1819
"@babel/helper-environment-visitor": "workspace:^",
1920
"@babel/helper-function-name": "workspace:^",
2021
"@babel/helper-optimise-call-expression": "workspace:^",

packages/babel-plugin-transform-classes/src/index.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { declare } from "@babel/helper-plugin-utils";
2+
import { isRequired } from "@babel/helper-compilation-targets";
23
import annotateAsPure from "@babel/helper-annotate-as-pure";
34
import nameFunction from "@babel/helper-function-name";
45
import splitExportDeclaration from "@babel/helper-split-export-declaration";
@@ -30,6 +31,10 @@ export default declare((api, options: Options) => {
3031
"superIsCallableConstructor",
3132
) ?? loose) as boolean;
3233
const noClassCalls = (api.assumption("noClassCalls") ?? loose) as boolean;
34+
const supportUnicodeId = !isRequired(
35+
"transform-unicode-escapes",
36+
api.targets(),
37+
);
3338

3439
// todo: investigate traversal requeueing
3540
const VISITED = new WeakSet();
@@ -59,7 +64,7 @@ export default declare((api, options: Options) => {
5964
const { node } = path;
6065
if (VISITED.has(node)) return;
6166

62-
const inferred = nameFunction(path);
67+
const inferred = nameFunction(path, undefined, supportUnicodeId);
6368
if (inferred && inferred !== node) {
6469
path.replaceWith(inferred);
6570
return;
@@ -68,12 +73,19 @@ export default declare((api, options: Options) => {
6873
VISITED.add(node);
6974

7075
const [replacedPath] = path.replaceWith(
71-
transformClass(path, state.file, builtinClasses, loose, {
72-
setClassMethods,
73-
constantSuper,
74-
superIsCallableConstructor,
75-
noClassCalls,
76-
}),
76+
transformClass(
77+
path,
78+
state.file,
79+
builtinClasses,
80+
loose,
81+
{
82+
setClassMethods,
83+
constantSuper,
84+
superIsCallableConstructor,
85+
noClassCalls,
86+
},
87+
supportUnicodeId,
88+
),
7789
);
7890

7991
if (replacedPath.isCallExpression()) {

packages/babel-plugin-transform-classes/src/transformClass.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export default function transformClass(
9898
builtinClasses: ReadonlySet<string>,
9999
isLoose: boolean,
100100
assumptions: ClassAssumptions,
101+
supportUnicodeId: boolean,
101102
) {
102103
const classState: State = {
103104
parent: undefined,
@@ -509,7 +510,14 @@ export default function transformClass(
509510
if (node.kind === "method") {
510511
// @ts-expect-error Fixme: we are passing a ClassMethod to nameFunction, but nameFunction
511512
// does not seem to support it
512-
fn = nameFunction({ id: key, node: node, scope });
513+
fn =
514+
nameFunction(
515+
// @ts-expect-error Fixme: we are passing a ClassMethod to nameFunction, but nameFunction
516+
// does not seem to support it
517+
{ id: key, node: node, scope },
518+
undefined,
519+
supportUnicodeId,
520+
) ?? fn;
513521
}
514522
} else {
515523
// todo(flow->ts) find a way to avoid "key as t.StringLiteral" below which relies on this assignment
@@ -571,11 +579,17 @@ export default function transformClass(
571579

572580
const key = t.toComputedKey(node, node.key);
573581
if (t.isStringLiteral(key)) {
574-
func = nameFunction({
575-
node: func,
576-
id: key,
577-
scope,
578-
});
582+
// @ts-expect-error: requires strictNullCheck
583+
func =
584+
nameFunction(
585+
{
586+
node: func,
587+
id: key,
588+
scope,
589+
},
590+
undefined,
591+
supportUnicodeId,
592+
) ?? func;
579593
}
580594

581595
const expr = t.expressionStatement(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var o = class { 𠮷野家() {} };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"targets": "chrome 43",
3+
"plugins": ["transform-classes"]
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var o = /*#__PURE__*/function () {
2+
"use strict";
3+
4+
function o() {
5+
babelHelpers.classCallCheck(this, o);
6+
}
7+
8+
var _proto = o.prototype;
9+
10+
_proto.𠮷野家 = function () {};
11+
12+
return babelHelpers.createClass(o);
13+
}();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var o = class { 𠮷野家() {} };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var o = class { 𠮷野家() {} };

0 commit comments

Comments
 (0)