diff --git a/src/evaluate.ts b/src/evaluate.ts index f4ce3198..94ea093f 100644 --- a/src/evaluate.ts +++ b/src/evaluate.ts @@ -652,22 +652,15 @@ const evaluate_map: EvaluateMap = { MemberExpression(path) { const {node, scope, ctx} = path; const {object, property, computed} = node; - if (types.isSuper(node.object)) { - const $var = scope.$find("this"); - if ($var) { - const __this = $var.$get(); - if (ctx.SuperClass) { - return ctx.SuperClass.prototype[(property).name].bind(__this); - } else { - throw node; - } - } - } - if (computed) { - return evaluate(path.$child(object))[evaluate(path.$child(property))]; - } else { - return evaluate(path.$child(object))[(property).name]; - } + + const propertyName: string = computed + ? evaluate(path.$child(property)) + : (property).name; + + const obj = evaluate(path.$child(object)); + const target = obj[propertyName]; + + return typeof target === "function" ? target.bind(obj) : target; }, AssignmentExpression(path) { const {node, scope} = path; @@ -781,10 +774,10 @@ const evaluate_map: EvaluateMap = { } }; - Object.defineProperties(func,{ - length:{value:node.params.length}, - name: {value:node.id ? node.id.name : ""} - }) + Object.defineProperties(func, { + length: {value: node.params.length}, + name: {value: node.id ? node.id.name : ""} + }); return func; }, @@ -800,96 +793,75 @@ const evaluate_map: EvaluateMap = { return path.node.value.raw; }, ClassDeclaration(path) { + const ClassConstructor = evaluate( + path.$child(path.node.body, path.scope.$child("block")) + ); + path.scope.$const(path.node.id.name, ClassConstructor); + }, + ClassBody(path) { const {node, scope} = path; - const constructor: types.ClassMethod | void = node.body.body.find( + const constructor: types.ClassMethod | void = node.body.find( n => types.isClassMethod(n) && n.kind === "constructor" ); - const methods: types.ClassMethod[] = node.body.body.filter( + const methods: types.ClassMethod[] = node.body.filter( n => types.isClassMethod(n) && n.kind !== "constructor" ); - const properties: types.ClassProperty[] = node.body.body.filter( + const properties: types.ClassProperty[] = node.body.filter( n => types.isClassProperty(n) ); - let SuperClass; - - if (node.superClass) { - const superClassName: string = (node.superClass).name; - const $var = scope.$find(superClassName); - - if ($var) { - SuperClass = $var.$get(); - } else { - throw new ErrNotDefined(superClassName); - } - } + const parentNode = (>path.parent).node; const Class = (function(SuperClass) { if (SuperClass) { _inherits(Class, SuperClass); } + function Class(...args) { _classCallCheck(this, Class); - - const newScope = scope.$child("function"); - - // babel way to call super(); - const __this = _possibleConstructorReturn( - this, - ((Class).__proto__ || Object.getPrototypeOf(Class)).apply( - this, - args - ) - ); - - newScope.$const("super", this.__proto__); - - // typescript way to call super() - // const __this = superClass ? superClass.apply(this, args) || this : this; - - newScope.$const("this", __this); + const classScope = scope.$child("function"); + classScope.$var("this", this); // define class property properties.forEach(p => { - __this[p.key.name] = evaluate(path.$child(p.value, newScope)); + this[p.key.name] = evaluate(path.$child(p.value, classScope)); }); if (constructor) { // defined the params constructor.params.forEach((p: types.LVal, i) => { if (types.isIdentifier(p)) { - newScope.$const(p.name, args[i]); + classScope.$const(p.name, args[i]); } else { throw new Error("Invalid params"); } }); + constructor.body.body.forEach(n => + evaluate( + path.$child(n, classScope, { + SuperClass, + ClassConstructor: Class, + ClassConstructorArguments: args, + ClassEntity: this + }) + ) + ); - if (node.superClass) { - // make sure super exist in first line - const superCallExpression: types.ExpressionStatement = (constructor).body.body.shift(); - - if ( - !types.isExpressionStatement(superCallExpression) || - !types.isCallExpression(superCallExpression.expression) || - !types.isSuper(superCallExpression.expression.callee) - ) { + if (parentNode.superClass) { + // if not apply super in construtor + // FIXME: should define the var in private scope + if (!scope.$find("@super")) { throw ErrNoSuper; - } else { - // TODO: run super } } - - constructor.body.body.forEach(n => - evaluate(path.$child(n, newScope, {SuperClass})) - ); } - return __this; + return this; } // define class name Object.defineProperties(Class, { - name: {value: node.id.name}, + name: {value: parentNode.id.name}, length: {value: constructor ? constructor.params.length : 0} }); @@ -907,7 +879,12 @@ const evaluate_map: EvaluateMap = { }); const result = evaluate( - path.$child(method.body, newScope, {SuperClass}) + path.$child(method.body, newScope, { + SuperClass, + ClassConstructor: Class, + ClassMethodArguments: args, + ClassEntity: this + }) ); if (result === RETURN_SINGAL) { return result.result ? result.result : result; @@ -934,17 +911,54 @@ const evaluate_map: EvaluateMap = { _createClass(Class, _methods); return Class; - })(SuperClass); + })( + parentNode.superClass + ? (() => { + const $var = scope.$find((parentNode.superClass).name); + return $var ? $var.$get() : null; + })() + : null + ); - scope.$const(node.id.name, Class); + return Class; }, ClassMethod(path) { return evaluate(path.$child(path.node.body)); }, ClassExpression(path) {}, Super(path) { - // FIXME: check it include in Class expression - return function() {}; + const {scope, ctx} = path; + const { + SuperClass, + ClassConstructor, + ClassConstructorArguments, + ClassEntity + } = ctx; + const ClassBodyPath = path.$findParent("ClassBody"); + // make sure it include in ClassDeclaration + if (!ClassBodyPath) { + throw new Error("super() only can use in ClassDeclaration"); + } + const parentPath = path.parent; + if (parentPath) { + // super() + if (types.isCallExpression(parentPath.node)) { + return function inherits(...args) { + _possibleConstructorReturn( + ClassEntity, + ( + (ClassConstructor).__proto__ || + Object.getPrototypeOf(ClassConstructor) + ).apply(ClassEntity, args) + ); + ClassBodyPath.scope.$const("@super", true); + }.bind(ClassEntity); + } else if (types.isMemberExpression(parentPath.node)) { + // super.eat() + // then return the superclass prototype + return SuperClass.prototype; + } + } }, SpreadElement(path) { return evaluate(path.$child(path.node.argument)); diff --git a/src/path.ts b/src/path.ts index adbb3d63..dfb7d2b8 100644 --- a/src/path.ts +++ b/src/path.ts @@ -1,15 +1,10 @@ -import { - Node, - ClassDeclaration, - ClassExpression, - CallExpression -} from "babel-types"; +import {Node} from "babel-types"; import {Scope, ScopeType} from "./scope"; export class Path { constructor( public node: T, - public parent: Node | null, + public parent: Path | null, public scope: Scope, public ctx: any = {} ) {} @@ -20,11 +15,20 @@ export class Path { ): Path { return new Path( node, - this.parent, + this, scope ? typeof scope === "string" ? this.scope.$child(scope) : scope : this.scope, {...this.ctx, ...ctx} ); } + $findParent(type: string): Path | null { + if (this.parent) { + return this.parent.node.type === type + ? this.parent + : this.parent.$findParent(type); + } else { + return null; + } + } } diff --git a/src/type.ts b/src/type.ts index 6eeaa9a1..e6d7aeba 100644 --- a/src/type.ts +++ b/src/type.ts @@ -65,7 +65,7 @@ export interface NodeTypeMap { // ArrayPattern: types.ArrayPattern; RestElement: types.RestElement; AssignmentPattern: types.AssignmentPattern; - // ClassBody: types.ClassBody; + ClassBody: types.ClassBody; ImportDeclaration: types.ImportDeclaration; ExportNamedDeclaration: types.ExportNamedDeclaration; ExportDefaultDeclaration: types.ExportDefaultDeclaration; diff --git a/test/ClassDeclaration.test.ts b/test/ClassDeclaration.test.ts index 1caf7dbd..b826d9e5 100644 --- a/test/ClassDeclaration.test.ts +++ b/test/ClassDeclaration.test.ts @@ -144,7 +144,7 @@ module.exports = { t.deepEqual(typeof people.learn, "function"); }); -test("ClassDeclaration-extends and super", t => { +test("ClassDeclaration-extends and super-1", t => { const sandbox: any = vm.createContext({}); const {Life, People} = vm.runInContext( @@ -191,7 +191,7 @@ module.exports = { t.true(people.learn()); // inherit from Life getSkill method }); -test("ClassDeclaration-extends and super", t => { +test("ClassDeclaration-extends and super-2", t => { const sandbox: any = vm.createContext({}); const {People} = vm.runInContext( @@ -243,14 +243,42 @@ test("ClassDeclaration-extends without super", t => { t.throws(function() { vm.runInContext( ` - class Life{ +class Life{ +} +class People extends Life{ + constructor(name){ } - class People extends Life{ - constructor(name){ - } +} + +module.exports = new People(); + `, + sandbox + ); + }, ErrNoSuper.message); +}); + +test("ClassDeclaration-extends without super", t => { + const sandbox: any = vm.createContext({}); + + t.throws(function() { + vm.runInContext( + ` +class Life{ +} +class People extends Life{ + constructor(name){ + super() } +} + +class A extends Life{ + constructor(name){ + // here will throw an error + } +} - module.exports = new People(); +new People(); +new A(); // throw error `, sandbox );