Define class elements in the correct order (#12723)

* Define class elements in the correct order

* Object.gOPDescriptors is not available on Node.js 6

* Handle numeric keys

* Update test

* Update fixtures
This commit is contained in:
Nicolò Ribaudo 2021-02-02 02:22:16 +01:00 committed by GitHub
parent 20664a430e
commit 60ef190d05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 273 additions and 104 deletions

View File

@ -20,16 +20,6 @@ var Point = /*#__PURE__*/function () {
} }
babelHelpers.createClass(Point, [{ babelHelpers.createClass(Point, [{
key: "equals",
value: function equals(p) {
return babelHelpers.classPrivateFieldLooseBase(this, _x)[_x] === babelHelpers.classPrivateFieldLooseBase(p, _x)[_x] && babelHelpers.classPrivateFieldLooseBase(this, _y)[_y] === babelHelpers.classPrivateFieldLooseBase(p, _y)[_y];
}
}, {
key: "toString",
value: function toString() {
return `Point<${babelHelpers.classPrivateFieldLooseBase(this, _x)[_x]},${babelHelpers.classPrivateFieldLooseBase(this, _y)[_y]}>`;
}
}, {
key: "x", key: "x",
get: function () { get: function () {
return babelHelpers.classPrivateFieldLooseBase(this, _x)[_x]; return babelHelpers.classPrivateFieldLooseBase(this, _x)[_x];
@ -45,6 +35,16 @@ var Point = /*#__PURE__*/function () {
set: function (value) { set: function (value) {
babelHelpers.classPrivateFieldLooseBase(this, _y)[_y] = +value; babelHelpers.classPrivateFieldLooseBase(this, _y)[_y] = +value;
} }
}, {
key: "equals",
value: function equals(p) {
return babelHelpers.classPrivateFieldLooseBase(this, _x)[_x] === babelHelpers.classPrivateFieldLooseBase(p, _x)[_x] && babelHelpers.classPrivateFieldLooseBase(this, _y)[_y] === babelHelpers.classPrivateFieldLooseBase(p, _y)[_y];
}
}, {
key: "toString",
value: function toString() {
return `Point<${babelHelpers.classPrivateFieldLooseBase(this, _x)[_x]},${babelHelpers.classPrivateFieldLooseBase(this, _y)[_y]}>`;
}
}]); }]);
return Point; return Point;
}(); }();

View File

@ -23,16 +23,6 @@ var Point = /*#__PURE__*/function () {
} }
babelHelpers.createClass(Point, [{ babelHelpers.createClass(Point, [{
key: "equals",
value: function equals(p) {
return babelHelpers.classPrivateFieldGet(this, _x) === babelHelpers.classPrivateFieldGet(p, _x) && babelHelpers.classPrivateFieldGet(this, _y) === babelHelpers.classPrivateFieldGet(p, _y);
}
}, {
key: "toString",
value: function toString() {
return `Point<${babelHelpers.classPrivateFieldGet(this, _x)},${babelHelpers.classPrivateFieldGet(this, _y)}>`;
}
}, {
key: "x", key: "x",
get: function () { get: function () {
return babelHelpers.classPrivateFieldGet(this, _x); return babelHelpers.classPrivateFieldGet(this, _x);
@ -48,6 +38,16 @@ var Point = /*#__PURE__*/function () {
set: function (value) { set: function (value) {
babelHelpers.classPrivateFieldSet(this, _y, +value); babelHelpers.classPrivateFieldSet(this, _y, +value);
} }
}, {
key: "equals",
value: function equals(p) {
return babelHelpers.classPrivateFieldGet(this, _x) === babelHelpers.classPrivateFieldGet(p, _x) && babelHelpers.classPrivateFieldGet(this, _y) === babelHelpers.classPrivateFieldGet(p, _y);
}
}, {
key: "toString",
value: function toString() {
return `Point<${babelHelpers.classPrivateFieldGet(this, _x)},${babelHelpers.classPrivateFieldGet(this, _y)}>`;
}
}]); }]);
return Point; return Point;
}(); }();

View File

@ -32,9 +32,6 @@ var MyClass = /*#__PURE__*/function () {
} }
babelHelpers.createClass(MyClass, [{ babelHelpers.createClass(MyClass, [{
key: _ref2,
value: function () {}
}, {
key: "whatever", key: "whatever",
get: function () {}, get: function () {},
set: function (value) {} set: function (value) {}
@ -44,9 +41,12 @@ var MyClass = /*#__PURE__*/function () {
}, { }, {
key: _computed2, key: _computed2,
set: function (value) {} set: function (value) {}
}], [{ }, {
key: 10, key: _ref2,
value: function () {} value: function () {}
}], [{
key: "10",
value: function _() {}
}]); }]);
return MyClass; return MyClass;
}(); }();

View File

@ -32,9 +32,6 @@ var MyClass = /*#__PURE__*/function () {
} }
babelHelpers.createClass(MyClass, [{ babelHelpers.createClass(MyClass, [{
key: _ref2,
value: function () {}
}, {
key: "whatever", key: "whatever",
get: function () {}, get: function () {},
set: function (value) {} set: function (value) {}
@ -44,9 +41,12 @@ var MyClass = /*#__PURE__*/function () {
}, { }, {
key: _computed2, key: _computed2,
set: function (value) {} set: function (value) {}
}], [{ }, {
key: 10, key: _ref2,
value: function () {} value: function () {}
}], [{
key: "10",
value: function _() {}
}]); }]);
return MyClass; return MyClass;
}(); }();

View File

@ -15,7 +15,6 @@
"main": "lib/index.js", "main": "lib/index.js",
"dependencies": { "dependencies": {
"@babel/helper-annotate-as-pure": "workspace:^7.10.4", "@babel/helper-annotate-as-pure": "workspace:^7.10.4",
"@babel/helper-define-map": "workspace:^7.10.4",
"@babel/helper-function-name": "workspace:^7.10.4", "@babel/helper-function-name": "workspace:^7.10.4",
"@babel/helper-optimise-call-expression": "workspace:^7.10.4", "@babel/helper-optimise-call-expression": "workspace:^7.10.4",
"@babel/helper-plugin-utils": "workspace:^7.10.4", "@babel/helper-plugin-utils": "workspace:^7.10.4",

View File

@ -4,7 +4,6 @@ import ReplaceSupers, {
environmentVisitor, environmentVisitor,
} from "@babel/helper-replace-supers"; } from "@babel/helper-replace-supers";
import optimiseCall from "@babel/helper-optimise-call-expression"; import optimiseCall from "@babel/helper-optimise-call-expression";
import * as defineMap from "@babel/helper-define-map";
import { traverse, template, types as t } from "@babel/core"; import { traverse, template, types as t } from "@babel/core";
import annotateAsPure from "@babel/helper-annotate-as-pure"; import annotateAsPure from "@babel/helper-annotate-as-pure";
@ -49,8 +48,6 @@ export default function transformClass(
userConstructorPath: undefined, userConstructorPath: undefined,
hasConstructor: false, hasConstructor: false,
instancePropBody: [],
instancePropRefs: {},
staticPropBody: [], staticPropBody: [],
body: [], body: [],
superThises: [], superThises: [],
@ -59,10 +56,21 @@ export default function transformClass(
protoAlias: null, protoAlias: null,
isLoose: false, isLoose: false,
hasInstanceDescriptors: false, methods: {
hasStaticDescriptors: false, // 'list' is in the same order as the elements appear in the class body.
instanceMutatorMap: {}, // if there aren't computed keys, we can safely reorder class elements
staticMutatorMap: {}, // and use 'map' to merge duplicates.
instance: {
hasComputed: false,
list: [],
map: new Map(),
},
static: {
hasComputed: false,
list: [],
map: new Map(),
},
},
}; };
const setState = newState => { const setState = newState => {
@ -78,25 +86,6 @@ export default function transformClass(
}, },
]); ]);
function pushToMap(node, enumerable, kind = "value", scope?) {
let mutatorMap;
if (node.static) {
setState({ hasStaticDescriptors: true });
mutatorMap = classState.staticMutatorMap;
} else {
setState({ hasInstanceDescriptors: true });
mutatorMap = classState.instanceMutatorMap;
}
const map = defineMap.push(mutatorMap, node, kind, classState.file, scope);
if (enumerable) {
map.enumerable = t.booleanLiteral(true);
}
return map;
}
/** /**
* Creates a class constructor or bail out if there is none * Creates a class constructor or bail out if there is none
*/ */
@ -202,48 +191,43 @@ export default function transformClass(
} }
} }
function clearDescriptors() {
setState({
hasInstanceDescriptors: false,
hasStaticDescriptors: false,
instanceMutatorMap: {},
staticMutatorMap: {},
});
}
function pushDescriptors() { function pushDescriptors() {
pushInheritsToBody(); pushInheritsToBody();
const { body } = classState; const { body } = classState;
let instanceProps; const props = {
let staticProps; instance: null,
static: null,
};
if (classState.hasInstanceDescriptors) { for (const placement of ["static", "instance"]) {
instanceProps = defineMap.toClassObject(classState.instanceMutatorMap); if (classState.methods[placement].list.length) {
props[placement] = classState.methods[placement].list.map(desc => {
const obj = t.objectExpression([
t.objectProperty(t.identifier("key"), desc.key),
]);
for (const kind of ["get", "set", "value"]) {
if (desc[kind] != null) {
obj.properties.push(
t.objectProperty(t.identifier(kind), desc[kind]),
);
}
}
return obj;
});
}
} }
if (classState.hasStaticDescriptors) { if (props.instance || props.static) {
staticProps = defineMap.toClassObject(classState.staticMutatorMap);
}
if (instanceProps || staticProps) {
if (instanceProps) {
instanceProps = defineMap.toComputedObjectFromClass(instanceProps);
}
if (staticProps) {
staticProps = defineMap.toComputedObjectFromClass(staticProps);
}
let args = [ let args = [
t.cloneNode(classState.classRef), // Constructor t.cloneNode(classState.classRef), // Constructor
t.nullLiteral(), // instanceDescriptors props.instance ? t.arrayExpression(props.instance) : t.nullLiteral(), // instanceDescriptors
t.nullLiteral(), // staticDescriptors props.static ? t.arrayExpression(props.static) : t.nullLiteral(), // staticDescriptors
]; ];
if (instanceProps) args[1] = instanceProps;
if (staticProps) args[2] = staticProps;
let lastNonNullIndex = 0; let lastNonNullIndex = 0;
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
if (!t.isNullLiteral(args[i])) lastNonNullIndex = i; if (!t.isNullLiteral(args[i])) lastNonNullIndex = i;
@ -256,8 +240,6 @@ export default function transformClass(
), ),
); );
} }
clearDescriptors();
} }
function wrapSuperCall(bareSuper, superRef, thisRef, body) { function wrapSuperCall(bareSuper, superRef, thisRef, body) {
@ -428,7 +410,45 @@ export default function transformClass(
if (processMethod(node, scope)) return; if (processMethod(node, scope)) return;
} }
pushToMap(node, false, null, scope); const placement = node.static ? "static" : "instance";
const methods = classState.methods[placement];
const descKey = node.kind === "method" ? "value" : node.kind;
const key =
t.isNumericLiteral(node.key) || t.isBigIntLiteral(node.key)
? t.stringLiteral(String(node.key.value))
: t.toComputedKey(node);
let fn = t.toExpression(node);
if (t.isStringLiteral(key)) {
// infer function name
if (node.kind === "method") {
fn = nameFunction({ id: key, node: node, scope });
}
} else {
methods.hasComputed = true;
}
let descriptor;
if (!methods.hasComputed && methods.map.has(key.value)) {
descriptor = methods.map.get(key.value);
descriptor[descKey] = fn;
if (descKey === "value") {
descriptor.get = null;
descriptor.set = null;
} else {
descriptor.value = null;
}
} else {
descriptor = { key: key, [descKey]: fn };
methods.list.push(descriptor);
if (!methods.hasComputed) {
methods.map.set(key.value, descriptor);
}
}
} }
function processMethod(node, scope) { function processMethod(node, scope) {

View File

@ -0,0 +1,21 @@
let x = 1;
class A {
get [x]() {}
[(x = 3, 2)]() {}
set [x](_) {}
}
expect(Object.getOwnPropertyDescriptors(A.prototype)).toMatchObject({
1: {
get: expect.any(Function),
set: undefined,
},
2: {
value: expect.any(Function),
},
3: {
get: undefined,
set: expect.any(Function),
}
});

View File

@ -0,0 +1,5 @@
class A {
get [x]() {}
[(x = 2, 3)]() {}
set [x](_) {}
}

View File

@ -0,0 +1,3 @@
{
"minNodeVersion": "8.0.0"
}

View File

@ -0,0 +1,26 @@
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
let A = /*#__PURE__*/function () {
"use strict";
function A() {
_classCallCheck(this, A);
}
_createClass(A, [{
key: x,
get: function () {}
}, {
key: (x = 2, 3),
value: function () {}
}, {
key: x,
set: function (_) {}
}]);
return A;
}();

View File

@ -0,0 +1,12 @@
class A {
get x() {}
x() {}
set x(_) {}
}
expect(Object.getOwnPropertyDescriptors(A.prototype)).toMatchObject({
x: {
get: undefined,
set: expect.any(Function),
}
});

View File

@ -0,0 +1,5 @@
class A {
get x() {}
x() {}
set x(_) {}
}

View File

@ -0,0 +1,3 @@
{
"minNodeVersion": "8.0.0"
}

View File

@ -0,0 +1,20 @@
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
let A = /*#__PURE__*/function () {
"use strict";
function A() {
_classCallCheck(this, A);
}
_createClass(A, [{
key: "x",
set: function (_) {}
}]);
return A;
}();

View File

@ -0,0 +1,17 @@
class A {
get 1() {}
set [1](_) {}
get 2() {}
set "2"(_) {}
get 3n() {}
set 3(_) {}
get [4n]() {}
set "4"(_) {}
// Different keys
get 5n() {}
set "5n"(_) {}
}

View File

@ -0,0 +1,40 @@
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
let A = /*#__PURE__*/function () {
"use strict";
function A() {
_classCallCheck(this, A);
}
_createClass(A, [{
key: "1",
get: function () {},
set: function (_) {}
}, {
key: "2",
get: function () {},
set: function (_) {}
}, {
key: "3",
get: function () {},
set: function (_) {}
}, {
key: "4",
get: function () {},
set: function (_) {} // Different keys
}, {
key: "5",
get: function () {}
}, {
key: "5n",
set: function (_) {}
}]);
return A;
}();

View File

@ -44,13 +44,13 @@ let Obj = /*#__PURE__*/function (_Base) {
} }
_createClass(Obj, [{ _createClass(Obj, [{
key: "test",
get: function () {}
}, {
key: "set", key: "set",
value: function set() { value: function set() {
return _set(_getPrototypeOf(Obj.prototype), "test", 3, this, true); return _set(_getPrototypeOf(Obj.prototype), "test", 3, this, true);
} }
}, {
key: "test",
get: function () {}
}]); }]);
return Obj; return Obj;

View File

@ -46,16 +46,16 @@ let Obj = /*#__PURE__*/function (_Base) {
} }
_createClass(Obj, [{ _createClass(Obj, [{
key: "set",
value: function set() {
return _set(_getPrototypeOf(Obj.prototype), "test", 3, this, true);
}
}, {
key: "test", key: "test",
set: function (v) { set: function (v) {
expect(this).toBe(obj); expect(this).toBe(obj);
value = v; value = v;
} }
}, {
key: "set",
value: function set() {
return _set(_getPrototypeOf(Obj.prototype), "test", 3, this, true);
}
}]); }]);
return Obj; return Obj;

View File

@ -1,6 +1,6 @@
class Foo { class Foo {
foo() {} foo() {}
"foo"() {} "foo"() { "second" }
[bar]() {} [bar]() {}
[bar + "foo"]() {} [bar + "foo"]() {}
} }

View File

@ -7,10 +7,9 @@ var Foo = /*#__PURE__*/function () {
babelHelpers.createClass(Foo, [{ babelHelpers.createClass(Foo, [{
key: "foo", key: "foo",
value: function foo() {} value: function foo() {
}, { "second";
key: "foo", }
value: function foo() {}
}, { }, {
key: bar, key: bar,
value: function value() {} value: function value() {}

View File

@ -1915,7 +1915,6 @@ __metadata:
dependencies: dependencies:
"@babel/core": "workspace:*" "@babel/core": "workspace:*"
"@babel/helper-annotate-as-pure": "workspace:^7.10.4" "@babel/helper-annotate-as-pure": "workspace:^7.10.4"
"@babel/helper-define-map": "workspace:^7.10.4"
"@babel/helper-function-name": "workspace:^7.10.4" "@babel/helper-function-name": "workspace:^7.10.4"
"@babel/helper-optimise-call-expression": "workspace:^7.10.4" "@babel/helper-optimise-call-expression": "workspace:^7.10.4"
"@babel/helper-plugin-test-runner": "workspace:*" "@babel/helper-plugin-test-runner": "workspace:*"