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
21 changed files with 273 additions and 104 deletions

View File

@@ -4,7 +4,6 @@ import ReplaceSupers, {
environmentVisitor,
} from "@babel/helper-replace-supers";
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 annotateAsPure from "@babel/helper-annotate-as-pure";
@@ -49,8 +48,6 @@ export default function transformClass(
userConstructorPath: undefined,
hasConstructor: false,
instancePropBody: [],
instancePropRefs: {},
staticPropBody: [],
body: [],
superThises: [],
@@ -59,10 +56,21 @@ export default function transformClass(
protoAlias: null,
isLoose: false,
hasInstanceDescriptors: false,
hasStaticDescriptors: false,
instanceMutatorMap: {},
staticMutatorMap: {},
methods: {
// 'list' is in the same order as the elements appear in the class body.
// if there aren't computed keys, we can safely reorder class elements
// and use 'map' to merge duplicates.
instance: {
hasComputed: false,
list: [],
map: new Map(),
},
static: {
hasComputed: false,
list: [],
map: new Map(),
},
},
};
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
*/
@@ -202,48 +191,43 @@ export default function transformClass(
}
}
function clearDescriptors() {
setState({
hasInstanceDescriptors: false,
hasStaticDescriptors: false,
instanceMutatorMap: {},
staticMutatorMap: {},
});
}
function pushDescriptors() {
pushInheritsToBody();
const { body } = classState;
let instanceProps;
let staticProps;
const props = {
instance: null,
static: null,
};
if (classState.hasInstanceDescriptors) {
instanceProps = defineMap.toClassObject(classState.instanceMutatorMap);
for (const placement of ["static", "instance"]) {
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) {
staticProps = defineMap.toClassObject(classState.staticMutatorMap);
}
if (instanceProps || staticProps) {
if (instanceProps) {
instanceProps = defineMap.toComputedObjectFromClass(instanceProps);
}
if (staticProps) {
staticProps = defineMap.toComputedObjectFromClass(staticProps);
}
if (props.instance || props.static) {
let args = [
t.cloneNode(classState.classRef), // Constructor
t.nullLiteral(), // instanceDescriptors
t.nullLiteral(), // staticDescriptors
props.instance ? t.arrayExpression(props.instance) : t.nullLiteral(), // instanceDescriptors
props.static ? t.arrayExpression(props.static) : t.nullLiteral(), // staticDescriptors
];
if (instanceProps) args[1] = instanceProps;
if (staticProps) args[2] = staticProps;
let lastNonNullIndex = 0;
for (let i = 0; i < args.length; i++) {
if (!t.isNullLiteral(args[i])) lastNonNullIndex = i;
@@ -256,8 +240,6 @@ export default function transformClass(
),
);
}
clearDescriptors();
}
function wrapSuperCall(bareSuper, superRef, thisRef, body) {
@@ -428,7 +410,45 @@ export default function transformClass(
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) {