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

Support decorator 2023-11 normative updates #16242

Merged
merged 13 commits into from Jan 31, 2024
  •  
  •  
  •  
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a follow-up PR, we should update all these version checks to only happen in Babel 7.

? 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