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

Fixed computed keys for class expression #10029

Merged
merged 6 commits into from May 28, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/babel-helper-create-class-features-plugin/src/misc.js
Expand Up @@ -85,6 +85,9 @@ export function injectInitialization(path, constructor, nodes, renamer) {
export function extractComputedKeys(ref, path, computedPaths, file) {
const declarations = [];

const nearestBlock = path.find(
parentPath => parentPath.isBlockStatement() || parentPath.isProgram(),
Copy link
Member

Choose a reason for hiding this comment

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

Sometimes the variable needs to be inserted where a block doesn't exist. Do we have a test for this code?

() => class { [k] = 2 }

Copy link
Member Author

Choose a reason for hiding this comment

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

);
for (const computedPath of computedPaths) {
computedPath.traverse(classFieldDefinitionEvaluationTDZVisitor, {
classBinding: path.node.id && path.scope.getBinding(path.node.id.name),
Expand All @@ -98,10 +101,14 @@ export function extractComputedKeys(ref, path, computedPaths, file) {
const ident = path.scope.generateUidIdentifierBasedOnNode(
computedNode.key,
);
nearestBlock.unshiftContainer(
"body",
t.variableDeclaration("let", [t.variableDeclarator(ident, null)]),
);
danez marked this conversation as resolved.
Show resolved Hide resolved
declarations.push(
t.variableDeclaration("var", [
t.variableDeclarator(ident, computedNode.key),
]),
t.expressionStatement(
t.assignmentExpression("=", t.cloneNode(ident), computedNode.key),
),
);
computedNode.key = t.cloneNode(ident);
}
Expand Down
Expand Up @@ -18,9 +18,10 @@ function (_Hello) {
function Outer() {
var _this;

babelHelpers.classCallCheck(this, Outer);
let _this2;

var _this2 = _this = babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(Outer).call(this));
babelHelpers.classCallCheck(this, Outer);
_this2 = _this = babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(Outer).call(this));

let Inner = function Inner() {
babelHelpers.classCallCheck(this, Inner);
Expand Down
Expand Up @@ -24,10 +24,11 @@ function (_Hello) {
function Outer() {
var _this;

let _babelHelpers$get$cal;

babelHelpers.classCallCheck(this, Outer);
_this = babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(Outer).call(this));

var _babelHelpers$get$cal = babelHelpers.get(babelHelpers.getPrototypeOf(Outer.prototype), "toString", babelHelpers.assertThisInitialized(_this)).call(babelHelpers.assertThisInitialized(_this));
_babelHelpers$get$cal = babelHelpers.get(babelHelpers.getPrototypeOf(Outer.prototype), "toString", babelHelpers.assertThisInitialized(_this)).call(babelHelpers.assertThisInitialized(_this));

let Inner = function Inner() {
babelHelpers.classCallCheck(this, Inner);
Expand Down
@@ -1,24 +1,35 @@
var foo = "foo";
var _ref4;

var bar = () => {};
var _baz;

var four = 4;
var _ref3;

var _ref2;

var _one = one();
var _computed2;

var _ref = 2 * four + seven;
var _computed;

var _undefined = undefined;
var _undefined;

var _computed = computed();
var _ref;

var _computed2 = computed();
var _one;

var _ref2 = "test" + one;
var foo = "foo";

var bar = () => {};

var _ref3 = /regex/;
var _baz = baz;
var _ref4 = `template${expression}`;
var four = 4;
_one = one();
_ref = 2 * four + seven;
_undefined = undefined;
_computed = computed();
_computed2 = computed();
_ref2 = "test" + one;
_ref3 = /regex/;
_baz = baz;
_ref4 = `template${expression}`;

var MyClass =
/*#__PURE__*/
Expand Down
@@ -1,5 +1,7 @@
function test(x) {
var _x = x;
var _x;

_x = x;

var F = function F() {
"use strict";
Expand Down
@@ -1,24 +1,35 @@
var foo = "foo";
var _ref4;

var bar = () => {};
var _baz;

var four = 4;
var _ref3;

var _ref2;

var _one = one();
var _computed2;

var _ref = 2 * four + seven;
var _computed;

var _undefined = undefined;
var _undefined;

var _computed = computed();
var _ref;

var _computed2 = computed();
var _one;

var _ref2 = "test" + one;
var foo = "foo";

var bar = () => {};

var _ref3 = /regex/;
var _baz = baz;
var _ref4 = `template${expression}`;
var four = 4;
_one = one();
_ref = 2 * four + seven;
_undefined = undefined;
_computed = computed();
_computed2 = computed();
_ref2 = "test" + one;
_ref3 = /regex/;
_baz = baz;
_ref4 = `template${expression}`;

var MyClass =
/*#__PURE__*/
Expand Down
@@ -1,5 +1,7 @@
function test(x) {
var _x = x;
var _x;

_x = x;

var F = function F() {
"use strict";
Expand Down
Expand Up @@ -77,7 +77,9 @@ class ComputedField extends Obj {
constructor() {
var _temp3;

var _ref = (_temp3 = super(), babelHelpers.defineProperty(this, "field", 1), _temp3);
let _ref;

_ref = (_temp3 = super(), babelHelpers.defineProperty(this, "field", 1), _temp3);

class B extends Obj {
constructor() {
Expand Down
@@ -0,0 +1,23 @@
const classes = [];
for (let i = 0; i <= 10; ++i) {
classes.push(
class A {
[i] = `computed field ${i}`;
static foo = `static field ${i}`;
#bar = `private field ${i}`;
getBar() {
return this.#bar;
}
}
);
}

for(let i=0; i<= 10; ++i) {
const clazz = classes[i];
expect(clazz.foo).toBe('static field ' + i);

const instance = new clazz();
expect(Object.getOwnPropertyNames(instance)).toEqual([String(i)])
expect(instance[i]).toBe('computed field ' + i);
expect(instance.getBar()).toBe('private field ' + i);
}
@@ -0,0 +1,13 @@
const classes = [];
for (let i = 0; i <= 10; ++i) {
classes.push(
class A {
[i] = `computed field ${i}`;
static foo = `static field ${i}`;
#bar = `private field ${i}`;
getBar() {
return this.#bar;
}
}
);
}
@@ -0,0 +1,3 @@
{
"plugins": ["external-helpers", "proposal-class-properties"]
}
@@ -0,0 +1,23 @@
const classes = [];

for (let i = 0; i <= 10; ++i) {
var _class, _temp, _bar;

let _i;

classes.push((_temp = (_i = i, _class = class A {
constructor() {
babelHelpers.defineProperty(this, _i, `computed field ${i}`);

_bar.set(this, {
writable: true,
value: `private field ${i}`
});
}

getBar() {
return babelHelpers.classPrivateFieldGet(this, _bar);
}

}), _bar = new WeakMap(), babelHelpers.defineProperty(_class, "foo", `static field ${i}`), _temp));
}
@@ -1,4 +1,4 @@
var _class, _descriptor, _Symbol$search, _temp;
var _class, _descriptor, _temp;

function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }

Expand All @@ -8,6 +8,8 @@ function _defineProperties(target, props) { for (var i = 0; i < props.length; i+

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

let _Symbol$search;

function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }

function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and set to use loose mode. ' + 'To use proposal-class-properties in spec mode with decorators, wait for ' + 'the next major version of decorators in stage 2.'); }
Expand Down
Expand Up @@ -2,9 +2,11 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

let _x$x;

function _classNameTDZError(name) { throw new Error("Class \"" + name + "\" cannot be referenced in computed property keys."); }

var _x$x = {
_x$x = {
x: (_classNameTDZError("A"), A) || 0
}.x;

Expand Down
Expand Up @@ -2,9 +2,11 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

let _ref;

function _classNameTDZError(name) { throw new Error("Class \"" + name + "\" cannot be referenced in computed property keys."); }

var _ref = (_classNameTDZError("C"), C) + 3;
_ref = (_classNameTDZError("C"), C) + 3;

let C = function C() {
"use strict";
Expand Down
@@ -1,6 +1,8 @@
let _ref;

function _classNameTDZError(name) { throw new Error("Class \"" + name + "\" cannot be referenced in computed property keys."); }

var _ref = (_classNameTDZError("C"), C) + 3;
_ref = (_classNameTDZError("C"), C) + 3;

class C {}

Expand Down
6 changes: 2 additions & 4 deletions packages/babel-traverse/src/path/modification.js
Expand Up @@ -24,9 +24,7 @@ export function insertBefore(nodes) {
) {
return parentPath.insertBefore(nodes);
} else if (
(this.isNodeType("Expression") &&
this.listKey !== "params" &&
this.listKey !== "arguments") ||
(this.isNodeType("Expression") && !this.isJSXElement()) ||
(parentPath.isForStatement() && this.key === "init")
) {
if (this.node) nodes.push(this.node);
Expand Down Expand Up @@ -220,7 +218,7 @@ export function unshiftContainer(listKey, nodes) {
key: 0,
});

return path.insertBefore(nodes);
return path._containerInsertBefore(nodes);
}

export function pushContainer(listKey, nodes) {
Expand Down
39 changes: 39 additions & 0 deletions packages/babel-traverse/test/modification.js
Expand Up @@ -36,6 +36,19 @@ describe("modification", function() {

expect(generateCode(rootPath)).toBe("function test(a) {\n b;\n}");
});

it("properly handles more than one arguments", function() {
const code = "foo(a, b);";
const ast = parse(code);
traverse(ast, {
CallExpression: function(path) {
path.pushContainer("arguments", t.identifier("d"));
expect(generateCode(path)).toBe("foo(a, b, d);");
path.pushContainer("arguments", t.stringLiteral("s"));
expect(generateCode(path)).toBe(`foo(a, b, d, "s");`);
},
});
});
});
describe("unshiftContainer", function() {
it("unshifts identifier into params", function() {
Expand Down Expand Up @@ -116,6 +129,32 @@ describe("modification", function() {
);
});

it("returns inserted path with nested JSXElement", function() {
const ast = parse("<div><span>foo</span></div>", {
plugins: ["jsx"],
});
let path;
traverse(ast, {
Program: function(_path) {
path = _path.get("body.0");
},
JSXElement: function(path) {
const tagName = path.node.openingElement.name.name;
if (tagName !== "span") return;
path.insertBefore(
t.JSXElement(
t.JSXOpeningElement(t.JSXIdentifier("div"), [], false),
t.JSXClosingElement(t.JSXIdentifier("div")),
[],
),
);
},
});
expect(generateCode(path)).toBe(
"<div><div></div><span>foo</span></div>;",
);
});

describe("when the parent is an export declaration inserts the node before", function() {
it("the ExportNamedDeclaration", function() {
const bodyPath = getPath("export function a() {}", {
Expand Down