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

fix: Computed properties should keep original definition order #15232

Merged
merged 12 commits into from Dec 5, 2022
Expand Up @@ -17,7 +17,8 @@
"babel-plugin"
],
"dependencies": {
"@babel/helper-plugin-utils": "workspace:^"
"@babel/helper-plugin-utils": "workspace:^",
"@babel/template": "workspace:^"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
Expand Down
55 changes: 43 additions & 12 deletions packages/babel-plugin-transform-computed-properties/src/index.ts
@@ -1,6 +1,7 @@
import { types as t } from "@babel/core";
import type { PluginPass } from "@babel/core";
import { declare } from "@babel/helper-plugin-utils";
import template from "@babel/template";
import type { Scope } from "@babel/traverse";

export interface Options {
Expand All @@ -26,6 +27,44 @@ export default declare((api, options: Options) => {
? pushComputedPropsLoose
: pushComputedPropsSpec;

function buildDefineAccessor(
state: PluginPass,
type: "get" | "set",
obj: t.Expression,
key: t.Expression,
fn: t.Expression,
) {
let helper: t.Identifier;
if (state.availableHelper("defineAccessor")) {
helper = state.addHelper("defineAccessor");
} else {
// Fallback for @babel/helpers <= 7.20.6, manually add helper function
SuperSodaSea marked this conversation as resolved.
Show resolved Hide resolved
const file = state.file;
helper = file.declarations["defineAccessor"];
if (!helper) {
helper = file.declarations["defineAccessor"] =
file.scope.generateUidIdentifier("defineAccessor");

const helperDeclaration = template.statement.ast`
function ${helper}(type, obj, key, fn) {
var desc = { configurable: true, enumerable: true };
desc[type] = fn;
return Object.defineProperty(obj, key, desc);
}
`;
const helperPath = file.path.unshiftContainer(
"body",
helperDeclaration,
)[0];
// TODO: NodePath#unshiftContainer should automatically register new bindings.
file.scope.registerDeclaration(helperPath);
}
helper = t.cloneNode(helper);
}

return t.callExpression(helper, [t.stringLiteral(type), obj, key, fn]);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now the helper function is directly added to the file scope if it is not available. For example:

var obj = {
  get ["x" + foo]() { return "heh"; }
};

The output would be:

- var obj = babelHelpers.defineAccessor("get", {}, "x" + foo, function () {
+ function _defineAccessor(type, obj, key, fn) {
+   var desc = {
+     configurable: true,
+     enumerable: true
+   };
+   desc[type] = fn;
+   return Object.defineProperty(obj, key, desc);
+ }
+ var obj = _defineAccessor("get", {}, "x" + foo, function () {
    return "heh";
  });


/**
* Get value of an object member under object expression.
* Returns a function expression if prop is a ObjectMethod.
Expand Down Expand Up @@ -71,27 +110,19 @@ export default declare((api, options: Options) => {
{ body, computedProps, initPropExpression, objId, state }: PropertyInfo,
prop: t.ObjectMethod,
) {
const kind = prop.kind as "get" | "set";
const key =
!prop.computed && t.isIdentifier(prop.key)
? t.stringLiteral(prop.key.name)
: prop.key;
const value = getValue(prop);

if (computedProps.length === 1) {
return t.callExpression(state.addHelper("defineAccessor"), [
t.stringLiteral(prop.kind),
initPropExpression,
key,
getValue(prop),
]);
return buildDefineAccessor(state, kind, initPropExpression, key, value);
} else {
body.push(
t.expressionStatement(
t.callExpression(state.addHelper("defineAccessor"), [
t.stringLiteral(prop.kind),
t.cloneNode(objId),
key,
getValue(prop),
]),
buildDefineAccessor(state, kind, t.cloneNode(objId), key, value),
),
);
}
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Expand Up @@ -2386,6 +2386,7 @@ __metadata:
"@babel/core": "workspace:^"
"@babel/helper-plugin-test-runner": "workspace:^"
"@babel/helper-plugin-utils": "workspace:^"
"@babel/template": "workspace:^"
peerDependencies:
"@babel/core": ^7.0.0-0
languageName: unknown
Expand Down