Skip to content

Commit

Permalink
Support decorator 2023-11 normative updates (babel#16242)
Browse files Browse the repository at this point in the history
* copy test cases from 2023-05

* update 2023-11 test options

* copy applyDecs2305 to applyDecs2311

* allow 2023-11 decorator version

* feat: support per-field intitializers

* update test fixtures

OVERWRITE=1 yarn jest decorators -t "2023 11"

* update generated helpers

* update other class tests

* expand field-initializers-after-methods case

* Add failing private flavour test

* update pipeline operator tests

* Don't run Babel 8 test with 2023-05 decorator

* add release todo item
  • Loading branch information
JLHwung authored and liuxingbaoyu committed Mar 5, 2024
1 parent 9be48d1 commit 0d5b334
Show file tree
Hide file tree
Showing 448 changed files with 11,172 additions and 45 deletions.
4 changes: 2 additions & 2 deletions Makefile.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Makefile.source.mjs
Expand Up @@ -450,12 +450,13 @@ target["bootstrap-flow"] = function () {

target["new-version-checklist"] = function () {
// eslint-disable-next-line no-constant-condition
if (0) {
if (1) {
console.log(
`
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!! !!!!!!
Update minVersion in packages/babel-helpers/src/helpers/applyDecs2311.ts
!!!!!! Write any important message here, and change the !!!!!!
!!!!!! if (0) above to if (1) !!!!!!
!!!!!! !!!!!!
Expand Down
138 changes: 116 additions & 22 deletions packages/babel-helper-create-class-features-plugin/src/decorators.ts
Expand Up @@ -28,7 +28,13 @@ type ClassElement =
| t.TSIndexSignature
| t.StaticBlock;

type DecoratorVersionKind = "2023-05" | "2023-01" | "2022-03" | "2021-12";
// TODO(Babel 8): Only keep 2023-11
export type DecoratorVersionKind =
| "2023-11"
| "2023-05"
| "2023-01"
| "2022-03"
| "2021-12";

function incrementId(id: number[], idx = id.length - 1): void {
// If index is -1, id needs an additional character, unshift A
Expand Down Expand Up @@ -181,7 +187,9 @@ function addProxyAccessorsFor(
const { static: isStatic } = element.node;

const thisArg =
version === "2023-05" && isStatic ? className : t.thisExpression();
(version === "2023-11" || version === "2023-05") && isStatic
? className
: t.thisExpression();

const getterBody = t.blockStatement([
t.returnStatement(
Expand Down Expand Up @@ -244,7 +252,7 @@ function extractProxyAccessorsFor(
targetKey: t.PrivateName,
version: DecoratorVersionKind,
): (t.FunctionExpression | t.ArrowFunctionExpression)[] {
if (version !== "2023-05" && version !== "2023-01") {
if (version !== "2023-11" && version !== "2023-05" && version !== "2023-01") {
return [
template.expression.ast`
function () {
Expand Down Expand Up @@ -295,6 +303,16 @@ function prependExpressionsToFieldInitializer(
initializer.replaceWith(maybeSequenceExpression(expressions));
}

function prependExpressionsToStaticBlock(
expressions: t.Expression[],
blockPath: NodePath<t.StaticBlock>,
) {
blockPath.unshiftContainer(
"body",
t.expressionStatement(maybeSequenceExpression(expressions)),
);
}

function prependExpressionsToConstructor(
expressions: t.Expression[],
constructorPath: NodePath<t.ClassMethod>,
Expand Down Expand Up @@ -391,7 +409,7 @@ function insertExpressionsAfterSuperCallAndOptimize(
}

/**
* Build a class constructor path from the given expressions. If the class is
* Build a class constructor node from the given expressions. If the class is
* derived, the constructor will call super() first to ensure that `this`
* in the expressions work as expected.
*
Expand Down Expand Up @@ -421,6 +439,12 @@ function createConstructorFromExpressions(
);
}

function createStaticBlockFromExpressions(expressions: t.Expression[]) {
return t.staticBlock([
t.expressionStatement(maybeSequenceExpression(expressions)),
]);
}

// 3 bits reserved to this (0-7)
const FIELD = 0;
const ACCESSOR = 1;
Expand Down Expand Up @@ -514,7 +538,7 @@ function generateDecorationList(
const hasOneThis = decoratorsThis.some(Boolean);
const decs: t.Expression[] = [];
for (let i = 0; i < decsCount; i++) {
if (version === "2023-05" && hasOneThis) {
if ((version === "2023-11" || version === "2023-05") && hasOneThis) {
decs.push(
decoratorsThis[i] || t.unaryExpression("void", t.numericLiteral(0)),
);
Expand All @@ -539,7 +563,10 @@ function generateDecorationExprs(

let flag = el.kind;
if (el.isStatic) {
flag += version === "2023-05" ? STATIC : STATIC_OLD_VERSION;
flag +=
version === "2023-11" || version === "2023-05"
? STATIC
: STATIC_OLD_VERSION;
}
if (hasThis) flag += DECORATORS_HAVE_THIS;

Expand Down Expand Up @@ -791,6 +818,9 @@ function transformClass(
element as NodePath<t.ClassAccessorProperty>,
state,
);
if (version === "2023-11") {
break;
}
/* fallthrough */
default:
if (element.node.static) {
Expand Down Expand Up @@ -860,7 +890,10 @@ function transformClass(
let needMemoise = false;
for (const decorator of decorators) {
const { expression } = decorator;
if (version === "2023-05" && t.isMemberExpression(expression)) {
if (
(version === "2023-11" || version === "2023-05") &&
t.isMemberExpression(expression)
) {
let object;
if (
t.isSuper(expression.object) ||
Expand Down Expand Up @@ -931,6 +964,7 @@ function transformClass(
let needsInstancePrivateBrandCheck = false;

let fieldInitializerAssignments = [];
let staticFieldInitializerAssignments = [];

if (hasElementDecorators) {
if (protoInitLocal) {
Expand All @@ -941,6 +975,16 @@ function transformClass(
}
for (const element of body) {
if (!isClassDecoratableElementPath(element)) {
if (
staticFieldInitializerAssignments.length > 0 &&
element.isStaticBlock()
) {
prependExpressionsToStaticBlock(
staticFieldInitializerAssignments,
element,
);
staticFieldInitializerAssignments = [];
}
continue;
}

Expand Down Expand Up @@ -991,8 +1035,8 @@ function transformClass(
constructorPath = element;
}

let locals: t.Identifier[];
if (hasDecorators) {
let locals: t.Identifier | t.Identifier[];
let privateMethods: Array<
t.FunctionExpression | t.ArrowFunctionExpression
>;
Expand Down Expand Up @@ -1039,7 +1083,7 @@ function transformClass(
version,
isComputed,
);
locals = newFieldInitId;
locals = [newFieldInitId];
}
} else if (kind === FIELD) {
const initId = element.scope.parent.generateDeclaredUidIdentifier(
Expand All @@ -1056,15 +1100,16 @@ function transformClass(
),
);

locals = initId;
locals = [initId];

if (isPrivate) {
privateMethods = extractProxyAccessorsFor(key, version);
}
} else if (isPrivate) {
locals = element.scope.parent.generateDeclaredUidIdentifier(
const callId = element.scope.parent.generateDeclaredUidIdentifier(
`call_${name}`,
);
locals = [callId];

const replaceSupers = new ReplaceSupers({
constantSuper,
Expand Down Expand Up @@ -1096,15 +1141,15 @@ function transformClass(
movePrivateAccessor(
element as NodePath<t.ClassPrivateMethod>,
t.cloneNode(key),
t.cloneNode(locals),
t.cloneNode(callId),
isStatic,
);
} else {
const node = element.node as t.ClassPrivateMethod;

// Unshift
path.node.body.body.unshift(
t.classPrivateProperty(key, t.cloneNode(locals), [], node.static),
t.classPrivateProperty(key, t.cloneNode(callId), [], node.static),
);

decoratedPrivateMethods.add(key.id.name);
Expand Down Expand Up @@ -1147,12 +1192,39 @@ function transformClass(
) {
prependExpressionsToFieldInitializer(
fieldInitializerAssignments,
element as NodePath<
t.ClassProperty | t.ClassPrivateProperty | t.ClassAccessorProperty
>,
element as NodePath<t.ClassProperty | t.ClassPrivateProperty>,
);
fieldInitializerAssignments = [];
}

if (
staticFieldInitializerAssignments.length > 0 &&
isStatic &&
(kind === FIELD || kind === ACCESSOR)
) {
prependExpressionsToFieldInitializer(
staticFieldInitializerAssignments,
element as NodePath<t.ClassProperty | t.ClassPrivateProperty>,
);
staticFieldInitializerAssignments = [];
}

if (hasDecorators && version === "2023-11") {
if (kind === FIELD || kind === ACCESSOR) {
const initExtraId = scopeParent.generateDeclaredUidIdentifier(
`init_extra_${name}`,
);
locals.push(initExtraId);
const initExtraCall = t.callExpression(t.cloneNode(initExtraId), [
t.thisExpression(),
]);
if (!isStatic) {
fieldInitializerAssignments.push(initExtraCall);
} else {
staticFieldInitializerAssignments.push(initExtraCall);
}
}
}
}
}

Expand Down Expand Up @@ -1182,6 +1254,12 @@ function transformClass(
fieldInitializerAssignments = [];
}

if (staticFieldInitializerAssignments.length > 0) {
path.node.body.body.push(
createStaticBlockFromExpressions(staticFieldInitializerAssignments),
);
}

const elementDecorations = generateDecorationExprs(
elementDecoratorInfo,
version,
Expand Down Expand Up @@ -1296,7 +1374,12 @@ function transformClass(
}

let { superClass } = originalClass;
if (superClass && (process.env.BABEL_8_BREAKING || version === "2023-05")) {
if (
superClass &&
(process.env.BABEL_8_BREAKING ||
version === "2023-11" ||
version === "2023-05")
) {
const id = path.scope.maybeGenerateMemoised(superClass);
if (id) {
originalClass.superClass = t.assignmentExpression("=", id, superClass);
Expand Down Expand Up @@ -1389,7 +1472,11 @@ function createLocalsAssignment(
}
}

if (process.env.BABEL_8_BREAKING || version === "2023-05") {
if (
process.env.BABEL_8_BREAKING ||
version === "2023-11" ||
version === "2023-05"
) {
if (
maybePrivateBranName ||
superClass ||
Expand All @@ -1407,7 +1494,11 @@ function createLocalsAssignment(
args.push(t.unaryExpression("void", t.numericLiteral(0)));
}
if (superClass) args.push(superClass);
rhs = t.callExpression(state.addHelper("applyDecs2305"), args);
if (version === "2023-11") {
rhs = t.callExpression(state.addHelper("applyDecs2311"), args);
} else {
rhs = t.callExpression(state.addHelper("applyDecs2305"), args);
}
} else if (version === "2023-01") {
if (maybePrivateBranName) {
args.push(
Expand Down Expand Up @@ -1658,14 +1749,17 @@ function isDecoratedAnonymousClassExpression(path: NodePath) {
export default function (
{ assertVersion, assumption }: PluginAPI,
{ loose }: Options,
// TODO(Babel 8): Only keep 2023-05
version: "2023-05" | "2023-01" | "2022-03" | "2021-12",
version: DecoratorVersionKind,
inherits: PluginObject["inherits"],
): PluginObject {
if (process.env.BABEL_8_BREAKING) {
assertVersion(process.env.IS_PUBLISH ? PACKAGE_JSON.version : "^7.21.0");
} else {
if (version === "2023-05" || version === "2023-01") {
if (
version === "2023-11" ||
version === "2023-05" ||
version === "2023-01"
) {
assertVersion("^7.21.0");
} else if (version === "2021-12") {
assertVersion("^7.16.0");
Expand Down
12 changes: 7 additions & 5 deletions packages/babel-helper-create-class-features-plugin/src/index.ts
Expand Up @@ -4,6 +4,7 @@ import type { NodePath } from "@babel/traverse";
import nameFunction from "@babel/helper-function-name";
import splitExportDeclaration from "@babel/helper-split-export-declaration";
import createDecoratorTransform from "./decorators.ts";
import type { DecoratorVersionKind } from "./decorators.ts";

import semver from "semver";

Expand Down Expand Up @@ -36,7 +37,7 @@ interface Options {
inherits?: PluginObject["inherits"];
manipulateOptions?: PluginObject["manipulateOptions"];
api?: PluginAPI;
decoratorVersion?: "2023-05" | "2023-01" | "2022-03" | "2021-12" | "2018-09";
decoratorVersion?: DecoratorVersionKind | "2018-09";
}

export function createClassFeaturePlugin({
Expand All @@ -50,13 +51,14 @@ export function createClassFeaturePlugin({
}: Options): PluginObject {
if (feature & FEATURES.decorators) {
if (process.env.BABEL_8_BREAKING) {
return createDecoratorTransform(api, { loose }, "2023-05", inherits);
return createDecoratorTransform(api, { loose }, "2023-11", inherits);
} else {
if (
decoratorVersion === "2021-12" ||
decoratorVersion === "2022-03" ||
decoratorVersion === "2023-11" ||
decoratorVersion === "2023-05" ||
decoratorVersion === "2023-01" ||
decoratorVersion === "2023-05"
decoratorVersion === "2022-03" ||
decoratorVersion === "2021-12"
) {
return createDecoratorTransform(
api,
Expand Down
5 changes: 5 additions & 0 deletions packages/babel-helpers/src/helpers-generated.ts
Expand Up @@ -48,6 +48,11 @@ export default Object.freeze({
"7.21.0",
'import checkInRHS from"checkInRHS";import setFunctionName from"setFunctionName";import toPropertyKey from"toPropertyKey";export default function applyDecs2305(e,t,r,n,o,a){function i(e,t,r){return function(n,o){return r&&r(n),e[t].call(n,o)}}function c(e,t){for(var r=0;r<e.length;r++)e[r].call(t);return t}function s(e,t,r,n){if("function"!=typeof e&&(n||void 0!==e))throw new TypeError(t+" must "+(r||"be")+" a function"+(n?"":" or undefined"));return e}function applyDec(e,t,r,n,o,a,c,u,l,f,p,d,h){function m(e){if(!h(e))throw new TypeError("Attempted to access private element on non-instance")}var y,v=t[0],g=t[3],b=!u;if(!b){r||Array.isArray(v)||(v=[v]);var w={},S=[],A=3===o?"get":4===o||d?"set":"value";f?(p||d?w={get:setFunctionName((function(){return g(this)}),n,"get"),set:function(e){t[4](this,e)}}:w[A]=g,p||setFunctionName(w[A],n,2===o?"":A)):p||(w=Object.getOwnPropertyDescriptor(e,n))}for(var P=e,j=v.length-1;j>=0;j-=r?2:1){var D=v[j],E=r?v[j-1]:void 0,I={},O={kind:["field","accessor","method","getter","setter","class"][o],name:n,metadata:a,addInitializer:function(e,t){if(e.v)throw new Error("attempted to call addInitializer after decoration was finished");s(t,"An initializer","be",!0),c.push(t)}.bind(null,I)};try{if(b)(y=s(D.call(E,P,O),"class decorators","return"))&&(P=y);else{var k,F;O.static=l,O.private=f,f?2===o?k=function(e){return m(e),w.value}:(o<4&&(k=i(w,"get",m)),3!==o&&(F=i(w,"set",m))):(k=function(e){return e[n]},(o<2||4===o)&&(F=function(e,t){e[n]=t}));var N=O.access={has:f?h.bind():function(e){return n in e}};if(k&&(N.get=k),F&&(N.set=F),P=D.call(E,d?{get:w.get,set:w.set}:w[A],O),d){if("object"==typeof P&&P)(y=s(P.get,"accessor.get"))&&(w.get=y),(y=s(P.set,"accessor.set"))&&(w.set=y),(y=s(P.init,"accessor.init"))&&S.push(y);else if(void 0!==P)throw new TypeError("accessor decorators must return an object with get, set, or init properties or void 0")}else s(P,(p?"field":"method")+" decorators","return")&&(p?S.push(P):w[A]=P)}}finally{I.v=!0}}return(p||d)&&u.push((function(e,t){for(var r=S.length-1;r>=0;r--)t=S[r].call(e,t);return t})),p||b||(f?d?u.push(i(w,"get"),i(w,"set")):u.push(2===o?w[A]:i.call.bind(w[A])):Object.defineProperty(e,n,w)),P}function u(e,t){return Object.defineProperty(e,Symbol.metadata||Symbol.for("Symbol.metadata"),{configurable:!0,enumerable:!0,value:t})}if(arguments.length>=6)var l=a[Symbol.metadata||Symbol.for("Symbol.metadata")];var f=Object.create(null==l?null:l),p=function(e,t,r,n){var o,a,i=[],s=function(t){return checkInRHS(t)===e},u=new Map;function l(e){e&&i.push(c.bind(null,e))}for(var f=0;f<t.length;f++){var p=t[f];if(Array.isArray(p)){var d=p[1],h=p[2],m=p.length>3,y=16&d,v=!!(8&d),g=0==(d&=7),b=h+"/"+v;if(!g&&!m){var w=u.get(b);if(!0===w||3===w&&4!==d||4===w&&3!==d)throw new Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: "+h);u.set(b,!(d>2)||d)}applyDec(v?e:e.prototype,p,y,m?"#"+h:toPropertyKey(h),d,n,v?a=a||[]:o=o||[],i,v,m,g,1===d,v&&m?s:r)}}return l(o),l(a),i}(e,t,o,f);return r.length||u(e,f),{e:p,get c(){var t=[];return r.length&&[u(applyDec(e,[r],n,e.name,5,f,t),f),c.bind(null,t,e)]}}}',
),
// size: 3287, gzip size: 1636
applyDecs2311: helper(
"7.23.0",
'import checkInRHS from"checkInRHS";import setFunctionName from"setFunctionName";import toPropertyKey from"toPropertyKey";export default function applyDecs2311(e,t,r,n,o,a){function i(e,t,r){return function(n,o){return r&&r(n),e[t].call(n,o)}}function c(e,t){for(var r=0;r<e.length;r++)e[r].call(t);return t}function s(e,t,r,n){if("function"!=typeof e&&(n||void 0!==e))throw new TypeError(t+" must "+(r||"be")+" a function"+(n?"":" or undefined"));return e}function applyDec(e,t,r,n,o,a,u,l,f,p,d,m,h){function y(e){if(!h(e))throw new TypeError("Attempted to access private element on non-instance")}var v,g=t[0],b=t[3],w=!l;if(!w){r||Array.isArray(g)||(g=[g]);var S={},A=[],P=3===o?"get":4===o||m?"set":"value";p?(d||m?S={get:setFunctionName((function(){return b(this)}),n,"get"),set:function(e){t[4](this,e)}}:S[P]=b,d||setFunctionName(S[P],n,2===o?"":P)):d||(S=Object.getOwnPropertyDescriptor(e,n))}for(var j=e,D=g.length-1;D>=0;D-=r?2:1){var E=g[D],I=r?g[D-1]:void 0,O={},k={kind:["field","accessor","method","getter","setter","class"][o],name:n,metadata:a,addInitializer:function(e,t){if(e.v)throw new Error("attempted to call addInitializer after decoration was finished");s(t,"An initializer","be",!0),u.push(t)}.bind(null,O)};try{if(w)(v=s(E.call(I,j,k),"class decorators","return"))&&(j=v);else{var F,N;k.static=f,k.private=p,p?2===o?F=function(e){return y(e),S.value}:(o<4&&(F=i(S,"get",y)),3!==o&&(N=i(S,"set",y))):(F=function(e){return e[n]},(o<2||4===o)&&(N=function(e,t){e[n]=t}));var T=k.access={has:p?h.bind():function(e){return n in e}};if(F&&(T.get=F),N&&(T.set=N),j=E.call(I,m?{get:S.get,set:S.set}:S[P],k),m){if("object"==typeof j&&j)(v=s(j.get,"accessor.get"))&&(S.get=v),(v=s(j.set,"accessor.set"))&&(S.set=v),(v=s(j.init,"accessor.init"))&&A.push(v);else if(void 0!==j)throw new TypeError("accessor decorators must return an object with get, set, or init properties or void 0")}else s(j,(d?"field":"method")+" decorators","return")&&(d?A.push(j):S[P]=j)}}finally{O.v=!0}}return(d||m)&&l.push((function(e,t){for(var r=A.length-1;r>=0;r--)t=A[r].call(e,t);return t}),c.bind(null,u)),d||w||(p?m?l.splice(-1,0,i(S,"get"),i(S,"set")):l.push(2===o?S[P]:i.call.bind(S[P])):Object.defineProperty(e,n,S)),j}function u(e,t){return Object.defineProperty(e,Symbol.metadata||Symbol.for("Symbol.metadata"),{configurable:!0,enumerable:!0,value:t})}if(arguments.length>=6)var l=a[Symbol.metadata||Symbol.for("Symbol.metadata")];var f=Object.create(null==l?null:l),p=function(e,t,r,n){var o,a,i=[],s=function(t){return checkInRHS(t)===e},u=new Map;function l(e){e&&i.push(c.bind(null,e))}for(var f=0;f<t.length;f++){var p=t[f];if(Array.isArray(p)){var d=p[1],m=p[2],h=p.length>3,y=16&d,v=!!(8&d),g=0==(d&=7),b=1===d,w=m+"/"+v;if(!g&&!h){var S=u.get(w);if(!0===S||3===S&&4!==d||4===S&&3!==d)throw new Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: "+m);u.set(w,!(d>2)||d)}applyDec(v?e:e.prototype,p,y,h?"#"+m:toPropertyKey(m),d,n,g||b?[]:v?a=a||[]:o=o||[],i,v,h,g,b,v&&h?s:r)}}return l(o),l(a),i}(e,t,o,f);return r.length||u(e,f),{e:p,get c(){var t=[];return r.length&&[u(applyDec(e,[r],n,e.name,5,f,t),f),c.bind(null,t,e)]}}}',
),
// size: 544, gzip size: 300
asyncGeneratorDelegate: helper(
"7.0.0-beta.0",
Expand Down

0 comments on commit 0d5b334

Please sign in to comment.