restructure classes transformer, fix class name inference - #1877

This commit is contained in:
Sebastian McKenzie 2015-06-30 14:49:04 +01:00
parent 092d98fb27
commit 336c65fe2c
12 changed files with 230 additions and 184 deletions

View File

@ -0,0 +1,23 @@
import LooseTransformer from "./loose";
import VanillaTransformer from "./vanilla";
import * as t from "../../../../types";
import { bare } from "../../../helpers/name-method";
export var visitor = {
ClassDeclaration(node) {
return t.variableDeclaration("let", [
t.variableDeclarator(node.id, t.toExpression(node))
]);
},
ClassExpression(node, parent, scope, file) {
var inferred = bare(node, parent, scope);
if (inferred) return inferred;
if (file.isLoose("es6.classes")) {
return new LooseTransformer(this, file).run();
} else {
return new VanillaTransformer(this, file).run();
}
}
};

View File

@ -0,0 +1,24 @@
import VanillaTransformer from "./vanilla";
import * as t from "../../../../types";
export default class LooseClassTransformer extends VanillaTransformer {
constructor() {
super(...arguments);
this.isLoose = true;
}
_processMethod(node) {
if (!node.decorators) {
// use assignments instead of define properties for loose classes
var classRef = this.classRef;
if (!node.static) classRef = t.memberExpression(classRef, t.identifier("prototype"));
var methodName = t.memberExpression(classRef, node.key, node.computed || t.isLiteral(node.key));
var expr = t.expressionStatement(t.assignmentExpression("=", methodName, node.value));
t.inheritsComments(expr, node);
this.body.push(expr);
return true;
}
}
}

View File

@ -1,27 +1,15 @@
import type NodePath from "../../../traversal/path"; import type NodePath from "../../../../traversal/path";
import type File from "../../file"; import type File from "../../../file";
import memoiseDecorators from "../../helpers/memoise-decorators"; import memoiseDecorators from "../../../helpers/memoise-decorators";
import ReplaceSupers from "../../helpers/replace-supers"; import ReplaceSupers from "../../../helpers/replace-supers";
import * as nameMethod from "../../helpers/name-method"; import * as nameMethod from "../../../helpers/name-method";
import * as defineMap from "../../helpers/define-map"; import * as defineMap from "../../../helpers/define-map";
import * as messages from "../../../messages"; import * as messages from "../../../../messages";
import * as util from "../../../util"; import * as util from "../../../../util";
import * as t from "../../../types"; import * as t from "../../../../types";
const PROPERTY_COLLISION_METHOD_NAME = "__initializeProperties"; const PROPERTY_COLLISION_METHOD_NAME = "__initializeProperties";
export var visitor = {
ClassDeclaration(node) {
return t.variableDeclaration("let", [
t.variableDeclarator(node.id, t.toExpression(node))
]);
},
ClassExpression(node, parent, scope, file) {
return new ClassTransformer(this, file).run();
}
};
var collectPropertyReferencesVisitor = { var collectPropertyReferencesVisitor = {
Identifier: { Identifier: {
enter(node, parent, scope, state) { enter(node, parent, scope, state) {
@ -83,7 +71,7 @@ var verifyConstructorVisitor = {
} }
}; };
class ClassTransformer { export default class ClassTransformer {
/** /**
* Description * Description
@ -96,11 +84,7 @@ class ClassTransformer {
this.path = path; this.path = path;
this.file = file; this.file = file;
this.hasInstanceDescriptors = false; this.clearDescriptors();
this.hasStaticDescriptors = false;
this.instanceMutatorMap = {};
this.staticMutatorMap = {};
this.instancePropBody = []; this.instancePropBody = [];
this.instancePropRefs = {}; this.instancePropRefs = {};
@ -108,15 +92,21 @@ class ClassTransformer {
this.body = []; this.body = [];
this.pushedConstructor = false; this.pushedConstructor = false;
this.hasConstructor = false; this.pushedInherits = false;
this.hasDecorators = false; this.hasDecorators = false;
this.isLoose = false;
// class id
this.className = this.node.id; this.className = this.node.id;
// this is the name of the binding that will **always** reference the class we've constructed
this.classRef = this.node.id || this.scope.generateUidIdentifier("class"); this.classRef = this.node.id || this.scope.generateUidIdentifier("class");
// this is a direct reference to the class we're building, class decorators can shadow the classRef
this.directRef = null;
this.superName = this.node.superClass || t.identifier("Function"); this.superName = this.node.superClass || t.identifier("Function");
this.hasSuper = !!this.node.superClass; this.hasSuper = !!this.node.superClass;
this.isLoose = file.isLoose("es6.classes");
} }
/** /**
@ -137,15 +127,7 @@ class ClassTransformer {
// //
var constructorBody = this.constructorBody = t.blockStatement([]); var constructorBody = this.constructorBody = t.blockStatement([]);
var constructor; var constructor = this.constructor = this.buildConstructor();
if (this.className) {
constructor = t.functionDeclaration(this.className, [], constructorBody);
} else {
constructor = t.functionExpression(null, [], constructorBody);
}
this.constructor = constructor;
// //
@ -160,19 +142,15 @@ class ClassTransformer {
closureParams.push(superName); closureParams.push(superName);
this.superName = superName; this.superName = superName;
body.push(t.expressionStatement(t.callExpression(file.addHelper("inherits"), [classRef, superName])));
} }
// //
var decorators = this.node.decorators; var decorators = this.node.decorators;
if (decorators) { if (decorators) {
// create a class reference to use later on
this.classRef = this.scope.generateUidIdentifier(classRef);
// this is so super calls and the decorators have access to the raw function // this is so super calls and the decorators have access to the raw function
body.push(t.variableDeclaration("var", [ this.directRef = this.scope.generateUidIdentifier(this.classRef);
t.variableDeclarator(this.classRef, classRef) } else {
])); this.directRef = this.classRef;
} }
// //
@ -181,26 +159,11 @@ class ClassTransformer {
// make sure this class isn't directly called // make sure this class isn't directly called
constructorBody.body.unshift(t.expressionStatement(t.callExpression(file.addHelper("class-call-check"), [ constructorBody.body.unshift(t.expressionStatement(t.callExpression(file.addHelper("class-call-check"), [
t.thisExpression(), t.thisExpression(),
this.classRef this.directRef
]))); ])));
// //
this.pushDecorators();
if (decorators) {
// reverse the decorators so we execute them in the right order
decorators = decorators.reverse();
for (var i = 0; i < decorators.length; i++) {
var decorator = decorators[i];
var decoratorNode = util.template("class-decorator", {
DECORATOR: decorator.expression,
CLASS_REF: classRef
}, true);
decoratorNode.expression._ignoreModulesRemap = true;
body.push(decoratorNode);
}
}
body = body.concat(this.staticPropBody); body = body.concat(this.staticPropBody);
@ -210,8 +173,7 @@ class ClassTransformer {
} }
// //
body.push(t.returnStatement(this.classRef));
body.push(t.returnStatement(classRef));
return t.callExpression( return t.callExpression(
t.functionExpression(null, closureParams, t.blockStatement(body)), t.functionExpression(null, closureParams, t.blockStatement(body)),
@ -219,6 +181,14 @@ class ClassTransformer {
); );
} }
/**
* Description
*/
buildConstructor() {
return t.functionDeclaration(this.classRef, [], this.constructorBody);
}
/** /**
* Description * Description
*/ */
@ -261,7 +231,7 @@ class ClassTransformer {
if (this.hasSuper) { if (this.hasSuper) {
constructor = util.template("class-derived-default-constructor"); constructor = util.template("class-derived-default-constructor");
} else { } else {
constructor = t.functionExpression(null, [], t.blockStatement()); constructor = t.functionExpression(null, [], t.blockStatement([]));
} }
this.path.get("body").unshiftContainer("body", t.methodDefinition( this.path.get("body").unshiftContainer("body", t.methodDefinition(
@ -277,10 +247,17 @@ class ClassTransformer {
buildBody() { buildBody() {
this.constructorMeMaybe(); this.constructorMeMaybe();
this.pushBody();
this.placePropertyInitializers();
this.pushDescriptors();
}
var constructorBody = this.constructorBody; /**
* Description
*/
pushBody() {
var classBodyPaths = this.path.get("body.body"); var classBodyPaths = this.path.get("body.body");
var body = this.body;
for (var path of (classBodyPaths: Array)) { for (var path of (classBodyPaths: Array)) {
var node = path.node; var node = path.node;
@ -296,7 +273,7 @@ class ClassTransformer {
var replaceSupers = new ReplaceSupers({ var replaceSupers = new ReplaceSupers({
methodPath: path, methodPath: path,
methodNode: node, methodNode: node,
objectRef: this.classRef, objectRef: this.directRef,
superRef: this.superName, superRef: this.superName,
isStatic: node.static, isStatic: node.static,
isLoose: this.isLoose, isLoose: this.isLoose,
@ -315,17 +292,29 @@ class ClassTransformer {
this.pushProperty(node, path); this.pushProperty(node, path);
} }
} }
//
this.placePropertyInitializers();
//
if (this.userConstructor) {
constructorBody.body = constructorBody.body.concat(this.userConstructor.body.body);
t.inherits(this.constructor, this.userConstructor);
t.inherits(this.constructorBody, this.userConstructor.body);
} }
/**
* Description
*/
clearDescriptors() {
this.hasInstanceDescriptors = false;
this.hasStaticDescriptors = false;
this.instanceMutatorMap = {};
this.staticMutatorMap = {};
}
/**
* Description
*/
pushDescriptors() {
this.pushInherits();
var body = this.body;
var instanceProps; var instanceProps;
var staticProps; var staticProps;
var classHelper = "create-class"; var classHelper = "create-class";
@ -372,8 +361,14 @@ class ClassTransformer {
t.callExpression(this.file.addHelper(classHelper), args) t.callExpression(this.file.addHelper(classHelper), args)
)); ));
} }
this.clearDescriptors();
} }
/**
* Description
*/
buildObjectAssignment(id) { buildObjectAssignment(id) {
return t.variableDeclaration("var", [ return t.variableDeclaration("var", [
t.variableDeclarator(id, t.objectExpression([])) t.variableDeclarator(id, t.objectExpression([]))
@ -406,11 +401,7 @@ class ClassTransformer {
} }
} else { } else {
if (this.hasSuper) { if (this.hasSuper) {
if (this.hasConstructor) {
this.bareSuper.insertAfter(body); this.bareSuper.insertAfter(body);
} else {
this.constructorBody.body = this.constructorBody.body.concat(body);
}
} else { } else {
this.constructorBody.body = body.concat(this.constructorBody.body); this.constructorBody.body = body.concat(this.constructorBody.body);
} }
@ -465,24 +456,16 @@ class ClassTransformer {
if (node.kind === "method") { if (node.kind === "method") {
nameMethod.property(node, this.file, path ? path.get("value").scope : this.scope); nameMethod.property(node, this.file, path ? path.get("value").scope : this.scope);
if (this._processMethod(node)) return;
if (this.isLoose && !node.decorators) {
// use assignments instead of define properties for loose classes
var classRef = this.classRef;
if (!node.static) classRef = t.memberExpression(classRef, t.identifier("prototype"));
var methodName = t.memberExpression(classRef, node.key, node.computed || t.isLiteral(node.key));
var expr = t.expressionStatement(t.assignmentExpression("=", methodName, node.value));
t.inheritsComments(expr, node);
this.body.push(expr);
return;
}
} }
this.pushToMap(node); this.pushToMap(node);
} }
_processMethod() {
return false;
}
/** /**
* Description * Description
*/ */
@ -549,13 +532,16 @@ class ClassTransformer {
fnPath.scope.rename(this.classRef.name); fnPath.scope.rename(this.classRef.name);
} }
var constructorBody = this.constructorBody;
var construct = this.constructor; var construct = this.constructor;
var fn = method.value; var fn = method.value;
this.userConstructorPath = fnPath; this.userConstructorPath = fnPath;
this.userConstructor = fn;
this.hasConstructor = true;
constructorBody.body = constructorBody.body.concat(fn.body.body);
t.inherits(constructorBody, fn.body);
t.inherits(construct, fn);
t.inheritsComments(construct, method); t.inheritsComments(construct, method);
construct._ignoreUserWhitespace = true; construct._ignoreUserWhitespace = true;
@ -564,21 +550,59 @@ class ClassTransformer {
t.inherits(construct.body, fn.body); t.inherits(construct.body, fn.body);
// push constructor to body // push constructor to body
if (!this.pushedConstructor) { this._pushConstructor();
}
_pushConstructor() {
if (this.pushedConstructor) return;
this.pushedConstructor = true; this.pushedConstructor = true;
if (this.className) { // we haven't pushed any descriptors yet
this.body.push(construct); if (this.hasInstanceDescriptors || this.hasStaticDescriptors) {
} else { this.pushDescriptors();
// infer class name if this is a nameless class expression }
this.constructor = nameMethod.bare(construct, this.parent, this.scope) || construct;
this.body.push(this.constructor);
this.pushInherits();
}
/**
* Push inherits helper to body.
*/
pushInherits() {
if (!this.hasSuper || this.pushedInherits) return;
this.pushedInherits = true;
this.body.push(t.expressionStatement(t.callExpression(
this.file.addHelper("inherits"),
[this.classRef, this.superName]
)));
}
/**
* Push decorators to body.
*/
pushDecorators() {
var decorators = this.node.decorators;
if (!decorators) return;
this.body.push(t.variableDeclaration("var", [ this.body.push(t.variableDeclaration("var", [
t.variableDeclarator(classRef, constructor) t.variableDeclarator(this.directRef, this.classRef)
])); ]));
t.inheritsComments(this.body[0], this.node); // reverse the decorators so we execute them in the right order
} decorators = decorators.reverse();
for (var decorator of (decorators: Array)) {
var decoratorNode = util.template("class-decorator", {
DECORATOR: decorator.expression,
CLASS_REF: this.classRef
}, true);
decoratorNode.expression._ignoreModulesRemap = true;
this.body.push(decoratorNode);
} }
} }
} }

View File

@ -11,7 +11,6 @@ var x = (function () {
4; 4;
5; 5;
6; 6;
babelHelpers.classCallCheck(this, x); babelHelpers.classCallCheck(this, x);
} }

View File

@ -6,26 +6,22 @@ var BaseView = function BaseView() {
this.autoRender = true; this.autoRender = true;
}; };
var BaseView = (function () { var BaseView = function BaseView() {
var _class = function BaseView() { babelHelpers.classCallCheck(this, BaseView);
babelHelpers.classCallCheck(this, _class);
this.autoRender = true; this.autoRender = true;
}; };
return _class;
})();
var BaseView = (function () { var BaseView = (function () {
var _class2 = function BaseView() { function BaseView() {
babelHelpers.classCallCheck(this, _class2); babelHelpers.classCallCheck(this, BaseView);
}; }
babelHelpers.createClass(_class2, [{ babelHelpers.createClass(BaseView, [{
key: "foo", key: "foo",
value: function foo() { value: function foo() {
this.autoRender = true; this.autoRender = true;
} }
}]); }]);
return _class2; return BaseView;
})(); })();

View File

@ -9,9 +9,9 @@ var TestEmpty = (function (_ref) {
babelHelpers.inherits(TestEmpty, _ref); babelHelpers.inherits(TestEmpty, _ref);
return TestEmpty; return TestEmpty;
})((function () { })((function () {
var _class = function _class() { function _class() {
babelHelpers.classCallCheck(this, _class); babelHelpers.classCallCheck(this, _class);
}; }
return _class; return _class;
})()); })());
@ -25,9 +25,9 @@ var TestConstructorOnly = (function (_ref2) {
babelHelpers.inherits(TestConstructorOnly, _ref2); babelHelpers.inherits(TestConstructorOnly, _ref2);
return TestConstructorOnly; return TestConstructorOnly;
})((function () { })((function () {
var _class2 = function _class2() { function _class2() {
babelHelpers.classCallCheck(this, _class2); babelHelpers.classCallCheck(this, _class2);
}; }
return _class2; return _class2;
})()); })());
@ -41,9 +41,9 @@ var TestMethodOnly = (function (_ref3) {
babelHelpers.inherits(TestMethodOnly, _ref3); babelHelpers.inherits(TestMethodOnly, _ref3);
return TestMethodOnly; return TestMethodOnly;
})((function () { })((function () {
var _class3 = function _class3() { function _class3() {
babelHelpers.classCallCheck(this, _class3); babelHelpers.classCallCheck(this, _class3);
}; }
babelHelpers.createClass(_class3, [{ babelHelpers.createClass(_class3, [{
key: "method", key: "method",
@ -61,9 +61,9 @@ var TestConstructorAndMethod = (function (_ref4) {
babelHelpers.inherits(TestConstructorAndMethod, _ref4); babelHelpers.inherits(TestConstructorAndMethod, _ref4);
return TestConstructorAndMethod; return TestConstructorAndMethod;
})((function () { })((function () {
var _class4 = function _class4() { function _class4() {
babelHelpers.classCallCheck(this, _class4); babelHelpers.classCallCheck(this, _class4);
}; }
babelHelpers.createClass(_class4, [{ babelHelpers.createClass(_class4, [{
key: "method", key: "method",
@ -81,9 +81,9 @@ var TestMultipleMethods = (function (_ref5) {
babelHelpers.inherits(TestMultipleMethods, _ref5); babelHelpers.inherits(TestMultipleMethods, _ref5);
return TestMultipleMethods; return TestMultipleMethods;
})((function () { })((function () {
var _class5 = function _class5() { function _class5() {
babelHelpers.classCallCheck(this, _class5); babelHelpers.classCallCheck(this, _class5);
}; }
babelHelpers.createClass(_class5, [{ babelHelpers.createClass(_class5, [{
key: "m1", key: "m1",

View File

@ -9,14 +9,10 @@ define(["exports", "module"], function (exports, module) {
module.exports = function () {}; module.exports = function () {};
var _default = (function () { var _default = function _default() {
var _class = function _default() { babelHelpers.classCallCheck(this, _default);
babelHelpers.classCallCheck(this, _class);
}; };
return _class;
})();
module.exports = _default; module.exports = _default;
function foo() {} function foo() {}

View File

@ -11,13 +11,9 @@ exports["default"] = foo;
exports["default"] = function () {}; exports["default"] = function () {};
var _default = (function () { var _default = function _default() {
var _class = function _default() { babelHelpers.classCallCheck(this, _default);
babelHelpers.classCallCheck(this, _class); };
};
return _class;
})();
exports["default"] = _default; exports["default"] = _default;

View File

@ -2,13 +2,9 @@
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var _default = (function () { var _default = function _default() {
var _class = function _default() { _classCallCheck(this, _default);
_classCallCheck(this, _class); };
};
return _class;
})();
function foo() {} function foo() {}

View File

@ -22,14 +22,10 @@ System.register([], function (_export) {
_export("default", function () {}); _export("default", function () {});
_default = (function () { _default = function _default() {
var _class = function _default() { _classCallCheck(this, _default);
_classCallCheck(this, _class);
}; };
return _class;
})();
_export("default", _default); _export("default", _default);
Foo = function Foo() { Foo = function Foo() {

View File

@ -21,14 +21,10 @@
module.exports = function () {}; module.exports = function () {};
var _default = (function () { var _default = function _default() {
var _class = function _default() { babelHelpers.classCallCheck(this, _default);
babelHelpers.classCallCheck(this, _class);
}; };
return _class;
})();
module.exports = _default; module.exports = _default;
function foo() {} function foo() {}

View File

@ -14,14 +14,14 @@ var Foo = (function (_Bar) {
})(Bar); })(Bar);
var Foo2 = (function (_Bar2) { var Foo2 = (function (_Bar2) {
var _class = function Foo2() { function Foo2() {
babelHelpers.classCallCheck(this, _class2); babelHelpers.classCallCheck(this, _Foo2);
babelHelpers.get(Object.getPrototypeOf(_class2.prototype), "constructor", this).call(this); babelHelpers.get(Object.getPrototypeOf(_Foo2.prototype), "constructor", this).call(this);
}; }
babelHelpers.inherits(_class, _Bar2); babelHelpers.inherits(Foo2, _Bar2);
var _class2 = _class; var _Foo2 = Foo2;
_class = bar(_class) || _class; Foo2 = bar(Foo2) || Foo2;
return _class; return Foo2;
})(Bar); })(Bar);