add class property initializers, add more TraversalPath flexibility with additional node injection method

This commit is contained in:
Sebastian McKenzie
2015-03-24 03:34:34 +11:00
parent 0ee230d13c
commit de88b28988
38 changed files with 425 additions and 103 deletions

View File

@@ -710,27 +710,29 @@ acorn.plugins.flow = function (instance) {
}
})
instance.extend("parseClassProperty", function (inner) {
return function (node) {
if (this.type === tt.colon) {
node.typeAnnotation = this.flow_parseTypeAnnotation()
}
return inner.call(this, node)
}
})
instance.extend("isClassProperty", function (inner) {
return function () {
return this.type === tt.colon || inner.call(this)
}
})
instance.extend("parseClassMethod", function (inner) {
return function (classBody, method, isGenerator, isAsync) {
var classProperty = false
if (this.type === tt.colon) {
method.typeAnnotation = this.flow_parseTypeAnnotation()
classProperty = true
}
if (classProperty) {
this.semicolon()
classBody.body.push(this.finishNode(method, "ClassProperty"))
} else {
var typeParameters
if (this.isRelational("<")) {
typeParameters = this.flow_parseTypeParameterDeclaration()
}
method.value = this.parseMethod(isGenerator, isAsync)
method.value.typeParameters = typeParameters
classBody.body.push(this.finishNode(method, "MethodDefinition"))
var typeParameters
if (this.isRelational("<")) {
typeParameters = this.flow_parseTypeParameterDeclaration()
}
method.value = this.parseMethod(isGenerator, isAsync)
method.value.typeParameters = typeParameters
classBody.body.push(this.finishNode(method, "MethodDefinition"))
}
})

View File

@@ -418,6 +418,7 @@ pp.parseParenAndDistinguishExpression = function(start, isAsync) {
par.expression = val
return this.finishNode(par, "ParenthesizedExpression")
} else {
val.parenthesizedExpression = true
return val
}
}

View File

@@ -9,6 +9,14 @@ const pp = Parser.prototype
// if possible.
pp.toAssignable = function(node, isBinding) {
if (node.parenthesizedExpression) {
if (node.type === "ObjectExpression") {
this.raise(node.start, "You're trying to assign to a parenthesized expression, instead of `({ foo }) = {}` use `({ foo } = {})`");
} else {
this.raise(node.start, "Parenthesized left hand expressions are illegal");
}
}
if (this.options.ecmaVersion >= 6 && node) {
switch (node.type) {
case "Identifier":

View File

@@ -150,10 +150,17 @@ pp.parseDebuggerStatement = function(node) {
}
pp.parseDoStatement = function(node) {
let start = this.markPosition()
this.next()
this.labels.push(loopLabel)
node.body = this.parseStatement(false)
this.labels.pop()
if (this.options.features["es7.doExpressions"] && this.type !== tt._while) {
let container = this.startNodeAt(start)
container.expression = this.finishNode(node, "DoExpression")
this.semicolon()
return this.finishNode(container, "ExpressionStatement")
}
this.expect(tt._while)
node.test = this.parseParenExpression()
if (this.options.ecmaVersion >= 6)
@@ -455,13 +462,17 @@ pp.parseClass = function(node, isStatement) {
while (!this.eat(tt.braceR)) {
if (this.eat(tt.semi)) continue
if (this.options.features["es7.decorators"] && this.type === tt.at) {
decorators.push(this.parseDecorator());
decorators.push(this.parseDecorator())
continue;
}
var method = this.startNode()
if (this.options.features["es7.decorators"] && decorators.length) {
method.decorators = decorators
decorators = []
}
var isGenerator = this.eat(tt.star), isAsync = false
this.parsePropertyName(method)
if (this.type !== tt.parenL && !method.computed && method.key.type === "Identifier" &&
if (this.options.features["es7.classProperties"] && this.type !== tt.parenL && !method.computed && method.key.type === "Identifier" &&
method.key.name === "static") {
if (isGenerator) this.unexpected()
method['static'] = true
@@ -470,6 +481,10 @@ pp.parseClass = function(node, isStatement) {
} else {
method['static'] = false
}
if (!isGenerator && method.key.type === "Identifier" && !method.computed && this.isClassProperty()) {
classBody.body.push(this.parseClassProperty(method))
continue
}
if (this.options.features["es7.asyncFunctions"] && this.type !== tt.parenL &&
!method.computed && method.key.type === "Identifier" && method.key.name === "async") {
isAsync = true
@@ -488,9 +503,8 @@ pp.parseClass = function(node, isStatement) {
method.kind = "constructor"
}
}
if (this.options.features["es7.decorators"] && decorators.length) {
method.decorators = decorators
decorators = []
if (method.kind === "constructor" && method.decorators) {
this.raise(method.start, "You can't attach decorators to a class constructor")
}
this.parseClassMethod(classBody, method, isGenerator, isAsync)
}
@@ -501,16 +515,32 @@ pp.parseClass = function(node, isStatement) {
return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression")
}
pp.parseClassMethod = function (classBody, method, isGenerator, isAsync) {
pp.isClassProperty = function() {
return this.type === tt.eq || (this.type === tt.semi || this.canInsertSemicolon())
}
pp.parseClassProperty = function(node) {
if (this.type === tt.eq) {
if (!this.options.features["es7.classProperties"]) this.unexpected()
this.next()
node.value = this.parseMaybeAssign();
} else {
node.value = null;
}
this.semicolon()
return this.finishNode(node, "ClassProperty")
}
pp.parseClassMethod = function(classBody, method, isGenerator, isAsync) {
method.value = this.parseMethod(isGenerator, isAsync)
classBody.body.push(this.finishNode(method, "MethodDefinition"))
}
pp.parseClassId = function (node, isStatement) {
pp.parseClassId = function(node, isStatement) {
node.id = this.type === tt.name ? this.parseIdent() : isStatement ? this.unexpected() : null
}
pp.parseClassSuper = function (node) {
pp.parseClassSuper = function(node) {
node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null
}

View File

@@ -19,6 +19,12 @@ export function UnaryExpression(node, print) {
print(node.argument);
}
export function DoExpression(node, print) {
this.push("do");
this.space();
print(node.body);
}
export function UpdateExpression(node, print) {
if (node.prefix) {
this.push(node.operator);

View File

@@ -51,7 +51,7 @@ export function toClassObject(mutatorMap) {
if (key[0] === "_") return;
var inheritNode = node;
if (t.isMethodDefinition(node)) node = node.value;
if (t.isMethodDefinition(node) || t.isClassProperty(node)) node = node.value;
var prop = t.property("init", t.identifier(key), node);
t.inheritsComments(prop, inheritNode);

View File

@@ -0,0 +1 @@
this.KEY = INITIALIZERS.KEY.call(this);

View File

@@ -0,0 +1 @@
CONSTRUCTOR.KEY = INITIALIZERS.KEY.call(CONSTRUCTOR);

View File

@@ -1,5 +1,5 @@
(function() {
function defineProperties(target, descriptors) {
function defineProperties(target, descriptors, initializers) {
for (var i = 0; i < descriptors.length; i ++) {
var descriptor = descriptors[i];
var decorators = descriptor.decorators;
@@ -23,15 +23,16 @@
throw new TypeError("The decorator for method " + descriptor.key + " is of the invalid type " + typeof decorator);
}
}
initializers[key] = descriptor.initializer;
}
Object.defineProperty(target, key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) {
if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers);
if (staticProps) defineProperties(Constructor, staticProps, staticInitializers);
return Constructor;
};
})()

View File

@@ -375,7 +375,7 @@ class BlockScoping {
var hasAsync = traverse.hasType(fn.body, this.scope, "AwaitExpression", t.FUNCTION_TYPES);
if (hasAsync) {
fn.async = true;
call = t.awaitExpression(call, true);
call = t.awaitExpression(call);
}
this.build(ret, call);

View File

@@ -4,6 +4,8 @@ import * as defineMap from "../../helpers/define-map";
import * as messages from "../../../messages";
import * as util from "../../../util";
import traverse from "../../../traversal";
import each from "lodash/collection/each";
import has from "lodash/object/has";
import * as t from "../../../types";
export var check = t.isClass;
@@ -35,6 +37,7 @@ var verifyConstructorVisitor = traverse.explode({
enter(node, parent, scope, state) {
if (this.get("callee").isSuper()) {
state.hasBareSuper = true;
state.bareSuper = this;
if (!state.hasSuper) {
throw this.errorWithNode("super call is only allowed in derived constructor");
@@ -65,14 +68,18 @@ class ClassTransformer {
this.path = path;
this.file = file;
this.hasInstanceMutators = false;
this.hasStaticMutators = false;
this.hasDecorators = false;
this.hasInstanceDescriptors = false;
this.hasStaticDescriptors = false;
this.instanceMutatorMap = {};
this.staticMutatorMap = {};
this.instancePropBody = [];
this.staticPropBody = [];
this.body = [];
this.hasConstructor = false;
this.hasDecorators = false;
this.className = this.node.id;
this.classRef = this.node.id || this.scope.generateUidIdentifier("class");
@@ -97,17 +104,11 @@ class ClassTransformer {
//
var body = this.body = [];
var body = this.body;
//
var constructorBody = t.blockStatement([
t.expressionStatement(t.callExpression(file.addHelper("class-call-check"), [
t.thisExpression(),
classRef
]))
]);
var constructorBody = this.constructorBody = t.blockStatement([]);
var constructor;
if (this.className) {
@@ -140,6 +141,13 @@ class ClassTransformer {
this.buildBody();
constructorBody.body.unshift(t.expressionStatement(t.callExpression(file.addHelper("class-call-check"), [
t.thisExpression(),
classRef
])));
//
var decorators = this.node.decorators;
if (decorators) {
for (var i = 0; i < decorators.length; i++) {
@@ -165,6 +173,8 @@ class ClassTransformer {
t.inheritsComments(body[0], this.node);
}
body = body.concat(this.staticPropBody);
//
body.push(t.returnStatement(classRef));
@@ -175,16 +185,69 @@ class ClassTransformer {
);
}
/**
* Description
*/
pushToMap(node, enumerable, kind = "value") {
var mutatorMap;
if (node.static) {
this.hasStaticDescriptors = true;
mutatorMap = this.staticMutatorMap;
} else {
this.hasInstanceDescriptors = true;
mutatorMap = this.instanceMutatorMap;
}
var alias = t.toKeyAlias(node);
//
var map = {};
if (has(mutatorMap, alias)) map = mutatorMap[alias];
mutatorMap[alias] = map;
//
map._inherits ||= [];
map._inherits.push(node);
map._key = node.key;
if (enumerable) {
map.enumerable = t.literal(true)
}
if (node.computed) {
map._computed = true;
}
if (node.decorators) {
this.hasDecorators = true;
var decorators = map.decorators ||= t.arrayExpression([]);
decorators.elements = decorators.elements.concat(node.decorators.map(dec => dec.expression));
}
if (map.value || map.initializer) {
throw this.file.errorWithNode(node, "Key conflict with sibling node");
}
if (node.kind === "get") kind = "get";
if (node.kind === "set") kind = "set";
map[kind] = node.value;
}
/**
* Description
*/
buildBody() {
var constructor = this.constructor;
var className = this.className;
var superName = this.superName;
var classBody = this.node.body.body;
var body = this.body;
var constructorBody = this.constructorBody;
var constructor = this.constructor;
var className = this.className;
var superName = this.superName;
var classBody = this.node.body.body;
var body = this.body;
var classBodyPaths = this.path.get("body").get("body");
@@ -220,22 +283,30 @@ class ClassTransformer {
if (!this.hasConstructor && this.hasSuper) {
var helperName = "class-super-constructor-call";
if (this.isLoose) helperName += "-loose";
constructor.body.body.push(util.template(helperName, {
constructorBody.body.push(util.template(helperName, {
CLASS_NAME: className,
SUPER_NAME: this.superName
}, true));
}
//
this.placePropertyInitializers();
//
if (this.userConstructor) {
constructorBody.body = constructorBody.body.concat(this.userConstructor.body.body);
}
var instanceProps;
var staticProps;
var classHelper = "create-class";
if (this.hasDecorators) classHelper = "create-decorated-class";
if (this.hasInstanceMutators) {
if (this.hasInstanceDescriptors) {
instanceProps = defineMap.toClassObject(this.instanceMutatorMap);
}
if (this.hasStaticMutators) {
if (this.hasStaticDescriptors) {
staticProps = defineMap.toClassObject(this.staticMutatorMap);
}
@@ -243,10 +314,30 @@ class ClassTransformer {
if (instanceProps) instanceProps = defineMap.toComputedObjectFromClass(instanceProps);
if (staticProps) staticProps = defineMap.toComputedObjectFromClass(staticProps);
instanceProps ||= t.literal(null);
var nullNode = t.literal(null);
// (Constructor, instanceDescriptors, staticDescriptors, instanceInitializers, staticInitializers)
var args = [this.classRef, nullNode, nullNode, nullNode, nullNode];
if (instanceProps) args[1] = instanceProps;
if (staticProps) args[2] = staticProps;
if (this.instanceInitializersId) {
args[3] = this.instanceInitializersId;
body.unshift(this.buildObjectAssignment(this.instanceInitializersId));
}
if (this.staticInitializersId) {
args[4] = this.staticInitializersId;
body.unshift(this.buildObjectAssignment(this.staticInitializersId));
}
var lastNonNullIndex = 0;
for (var i = 0; i < args.length; i++) {
if (args[i] !== nullNode) lastNonNullIndex = i;
}
args = args.slice(0, lastNonNullIndex + 1);
var args = [this.classRef, instanceProps];
if (staticProps) args.push(staticProps);
body.push(t.expressionStatement(
t.callExpression(this.file.addHelper(classHelper), args)
@@ -254,6 +345,32 @@ class ClassTransformer {
}
}
buildObjectAssignment(id) {
return t.variableDeclaration("var", [
t.variableDeclarator(id, t.objectExpression([]))
]);
}
/**
* Description
*/
placePropertyInitializers() {
var body = this.instancePropBody;
if (body.length) {
// todo: check for scope conflicts and shift into a method
if (this.hasSuper) {
if (this.bareSuper) {
this.bareSuper.insertAfter(body);
} else {
this.constructorBody.body = this.constructorBody.body.concat(body);
}
} else {
this.constructorBody.body = body.concat(this.constructorBody.body);
}
}
}
/**
* Description
*/
@@ -261,12 +378,15 @@ class ClassTransformer {
verifyConstructor(path: TraversalPath) {
var state = {
hasBareSuper: false,
bareSuper: null,
hasSuper: this.hasSuper,
file: this.file
};
path.traverse(verifyConstructorVisitor, state);
this.bareSuper = state.bareSuper;
if (!state.hasBareSuper && this.hasSuper) {
throw path.errorWithNode("Derived constructor must call super()");
}
@@ -277,11 +397,7 @@ class ClassTransformer {
*/
pushMethod(node: { type: "MethodDefinition" }) {
var methodName = node.key;
var kind = node.kind;
if (kind === "method") {
if (node.kind === "method") {
nameMethod.property(node, this.file, this.scope);
if (this.isLoose) {
@@ -289,56 +405,56 @@ class ClassTransformer {
var classRef = this.classRef;
if (!node.static) classRef = t.memberExpression(classRef, t.identifier("prototype"));
methodName = t.memberExpression(classRef, methodName, node.computed);
var methodName = t.memberExpression(classRef, node.key, node.computed);
var expr = t.expressionStatement(t.assignmentExpression("=", methodName, node.value));
t.inheritsComments(expr, node);
this.body.push(expr);
return;
}
kind = "value";
}
var mutatorMap = this.instanceMutatorMap;
if (node.static) {
this.hasStaticMutators = true;
mutatorMap = this.staticMutatorMap;
} else {
this.hasInstanceMutators = true;
}
defineMap.push(mutatorMap, methodName, kind, node.computed, node);
var decorators = node.decorators;
if (decorators && decorators.length) {
for (var i = 0; i < decorators.length; i++) {
decorators[i] = decorators[i].expression;
}
defineMap.push(mutatorMap, methodName, "decorators", node.computed, t.arrayExpression(decorators));
this.hasDecorators = true;
}
this.pushToMap(node);
}
/**
* Description
*/
pushProperty(node: Object) {
if (!node.value) return;
pushProperty(node: { type: "ClassProperty" }) {
if (!node.value && !node.decorators) return;
var key;
if (node.static) {
key = t.memberExpression(this.classRef, node.key);
this.body.push(
t.expressionStatement(t.assignmentExpression("=", key, node.value))
);
if (node.decorators) {
var body = [];
if (node.value) body.push(t.returnStatement(node.value));
node.value = t.functionExpression(null, [], t.blockStatement(body));
this.pushToMap(node, true, "initializer");
if (node.static) {
this.staticPropBody.push(util.template("call-static-decorator", {
INITIALIZERS: this.staticInitializersId ||= this.scope.generateUidIdentifier("staticInitializers"),
CONSTRUCTOR: this.classRef,
KEY: node.key,
}, true));
} else {
this.instancePropBody.push(util.template("call-instance-decorator", {
INITIALIZERS: this.instanceInitializersId ||= this.scope.generateUidIdentifier("instanceInitializers"),
KEY: node.key
}, true));
}
} else {
key = t.memberExpression(t.thisExpression(), node.key);
this.constructor.body.body.unshift(
t.expressionStatement(t.assignmentExpression("=", key, node.value))
);
if (node.static) {
// can just be added to the static map
this.pushToMap(node, true);
} else {
// add this to the instancePropBody which will be added after the super call in a derived constructor
// or at the start of a constructor for a non-derived constructor
this.instancePropBody.push(t.expressionStatement(
t.assignmentExpression("=", t.memberExpression(t.thisExpression(), node.key), node.value)
));
}
}
}
@@ -350,7 +466,8 @@ class ClassTransformer {
var construct = this.constructor;
var fn = method.value;
this.hasConstructor = true;
this.userConstructor = fn;
this.hasConstructor = true;
t.inherits(construct, fn);
t.inheritsComments(construct, method);
@@ -359,6 +476,5 @@ class ClassTransformer {
construct.params = fn.params;
t.inherits(construct.body, fn.body);
construct.body.body = construct.body.body.concat(fn.body.body);
}
}

View File

@@ -0,0 +1,8 @@
export var metadata = {
experimental: true,
optional: true
};
export function check() {
return false;
}

View File

@@ -5,6 +5,8 @@ export var metadata = {
optional: true
};
export var check = t.isDoExpression;
export function DoExpression(node) {
var body = node.body.body;
if (body.length) {

View File

@@ -1,4 +1,5 @@
export default {
"es7.classProperties": require("./es7/class-properties"),
"es7.asyncFunctions": require("./es7/async-functions"),
"es7.decorators": require("./es7/decorators"),
@@ -89,7 +90,7 @@ export default {
// needs to be before `_shadowFunctions`
"es6.arrowFunctions": require("./es6/arrow-functions"),
_shadowFunctions: require("./internal/alias-functions"),
_shadowFunctions: require("./internal/shadow-functions"),
"es7.doExpressions": require("./es7/do-expressions"),

View File

@@ -2,6 +2,10 @@ import * as t from "../../../types";
var functionChildrenVisitor = {
enter(node, parent, scope, state) {
if (this.isClass(node)) {
return this.skip();
}
if (this.isFunction() && !node.shadow) {
return this.skip();
}

View File

@@ -13,6 +13,7 @@ export function FunctionExpression() {
}
export { FunctionExpression as FunctionDeclaration };
export { FunctionExpression as Class };
export function ThisExpression() {
return t.identifier("undefined");

View File

@@ -34,6 +34,7 @@ export default class TraversalContext {
return;
}
// todo: handle nodes popping in and out of existence
for (var i = 0; i < nodes.length; i++) {
if (nodes[i] && this.visitNode(node, nodes, i)) {
return true;

View File

@@ -78,12 +78,41 @@ export default class TraversalPath {
return ourScope;
}
insertBefore(node) {
getParentArrayPath() {
var path = this;
while (!Array.isArray(path.container)) {
path = path.parentPath;
}
return path;
}
insertAfter(node) {
insertBefore(nodes) {
this.getParentArrayPath()._insertBefore(nodes);
}
insertAfter(nodes) {
this.getParentArrayPath()._insertAfter(nodes);
}
_insertBefore(nodes) {
throw new Error("to be implemented");
}
_insertAfter(nodes) {
// todo: in a statement context ie. BlockStatement or Program create individual statements instead
// of trying to join them
var key = this.key + 1;
this.container.splice(key, 0, null);
var paths = this.container._paths;
for (var i = 0; i > paths.length; i++) {
let path = paths[path];
if (path.key >= key) path.key++;
}
var path = TraversalPath.get(this.parentPath, null, this.parent, this.container, key, this.file);
path.setStatementsToExpression(nodes);
}
setData(key, val) {
@@ -172,7 +201,7 @@ export default class TraversalPath {
// inherit comments from original node to the first replacement node
var inheritTo = replacements[0];
if (inheritTo) t.inheritsComments(inheritTo, oldNode);
if (inheritTo && oldNode) t.inheritsComments(inheritTo, oldNode);
//
if (t.isStatement(replacements[0]) && t.isType(this.type, "Expression")) {
@@ -314,7 +343,12 @@ export default class TraversalPath {
} else { // "foo"
var path = this;
for (var i = 0; i > parts.length; i++) {
path = path.get(parts[i]);
var part = parts[i];
if (part === ".") {
path = path.parentPath;
} else {
path = path.get(parts[i]);
}
}
return path;
}

View File

@@ -49,7 +49,8 @@
"id": null,
"params": null,
"body": null,
"generator": false
"generator": false,
"async": false
},
"FunctionDeclaration": {

View File

@@ -72,8 +72,11 @@ export function getLastStatements(node: Object): Array<Object> {
if (t.isIfStatement(node)) {
add(node.consequent);
add(node.alternate);
} else if (t.isFor(node) || t.isWhile(node)) {
add(node.body);
} else if (t.isProgram(node) || t.isBlockStatement(node)) {
add(node.body[node.body.length - 1]);
} else if (t.isLoop()) {
} else if (node) {
nodes.push(node);
}

View File

@@ -1,7 +1,7 @@
{
"ArrayExpression": ["elements"],
"ArrayPattern": ["elements", "typeAnnotation"],
"ArrowFunctionExpression": ["params", "defaults", "rest", "body", "returnType"],
"ArrowFunctionExpression": ["params", "body", "returnType"],
"AssignmentExpression": ["left", "right"],
"AssignmentPattern": ["left", "right"],
"AwaitExpression": ["argument"],
@@ -31,8 +31,8 @@
"ForInStatement": ["left", "right", "body"],
"ForOfStatement": ["left", "right", "body"],
"ForStatement": ["init", "test", "update", "body"],
"FunctionDeclaration": ["id", "params", "defaults", "rest", "body", "returnType", "typeParameters"],
"FunctionExpression": ["id", "params", "defaults", "rest", "body", "returnType", "typeParameters"],
"FunctionDeclaration": ["id", "params", "body", "returnType", "typeParameters"],
"FunctionExpression": ["id", "params", "body", "returnType", "typeParameters"],
"Identifier": ["typeAnnotation"],
"IfStatement": ["test", "consequent", "alternate"],
"ImportDefaultSpecifier": ["local"],