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
Prev Previous commit
Next Next commit
Add fallback for helper function
  • Loading branch information
SuperSodaSea committed Dec 2, 2022
commit b8acd2e97fa1e40434e3a8248a5b47f40c0cdb52
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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