diff --git a/README.md b/README.md index 3ece15cf..5f77bde2 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ It base on [https://github.com/bramblex/jsjs](https://github.com/bramblex/jsjs) * [x] es2015-arrow-functions * [x] es2015-block-scoping * [x] es2015-classes - * [ ] es2015-computed-properties + * [x] es2015-computed-properties * [x] es2015-destructuring * [ ] es2015-for-of * [ ] es2015-function-name diff --git a/src/evaluate.ts b/src/evaluate.ts index 21aa414f..fdcc1992 100644 --- a/src/evaluate.ts +++ b/src/evaluate.ts @@ -386,43 +386,71 @@ const evaluate_map = { }, ObjectExpression(node: types.ObjectExpression, scope: Scope, arg) { let object = {}; + const newScope = scope.$child("block"); + const computedProperties: ( + | types.ObjectProperty + | types.ObjectMethod)[] = []; + + const newArg = {...arg, ...{object}}; + for (const property of node.properties) { - if (types.isObjectProperty(property)) { - if (types.isIdentifier(property.key)) { - object[property.key.name] = evaluate(property.value, scope, arg); - } else { - object[evaluate(property.key, scope, arg)] = evaluate( - property.value, - scope, - arg - ); - } - } else if (types.isObjectMethod(property)) { - switch (property.kind) { - case "get": - Object.defineProperty(object, evaluate(property.key, scope, arg), { - get: evaluate(property.value, scope, arg) - }); - break; - case "set": - Object.defineProperty(object, evaluate(property.key, scope, arg), { - set: evaluate(property.value, scope, arg) - }); - break; - case "method": - break; - default: - throw new Error("Invalid kind of property"); - } - } else if (types.isSpreadProperty(property)) { - // @experimental Object rest spread - object = Object.assign(object, evaluate(property.argument, scope, arg)); - } else { - throw node; + const _property = property; + if (_property.computed === true) { + computedProperties.push(_property); + continue; } + evaluate(property, newScope, newArg); } + + // eval the computed properties + for (const property of computedProperties) { + evaluate(property, newScope, newArg); + } + return object; }, + ObjectProperty(node: types.ObjectProperty, scope: Scope, arg) { + const {object} = arg; + const val = evaluate(node.value, scope, arg); + if (types.isIdentifier(node.key)) { + object[node.key.name] = val; + scope.$const(node.key.name, val); + } else { + object[evaluate(node.key, scope, arg)] = val; + } + }, + ObjectMethod(node: types.ObjectMethod, scope: Scope, arg) { + const key = evaluate(node.key, scope, arg); + const val = function() { + const _arguments = [].slice.call(arguments); + const newScope = scope.$child("function"); + newScope.$const("this", this); + // define argument + node.params.forEach((param, i) => { + if (types.isIdentifier(param)) { + newScope.$const(param.name, _arguments[i]); + } else { + throw node; + } + }); + const result = evaluate(node.body, newScope, arg); + return result.result ? result.result : result; + }; + Object.defineProperty(val, "length", {value: node.params.length}); + switch (node.kind) { + case "get": + Object.defineProperty(arg.object, key, {get: val}); + scope.$const(key, val); + break; + case "set": + Object.defineProperty(arg.object, key, {set: val}); + break; + case "method": + break; + default: + throw new Error("Invalid kind of property"); + } + }, FunctionExpression(node: types.FunctionExpression, scope: Scope, arg) { const func = function(...args) { const new_scope = scope.$child("function"); @@ -785,8 +813,10 @@ const evaluate_map = { SpreadElement(node: types.SpreadElement, scope: Scope, arg) { return evaluate(node.argument, scope, arg); }, - ObjectProperty(node: types.ObjectProperty, scope: Scope, arg) { - // do nothing + // @experimental Object rest spread + SpreadProperty(node: types.SpreadProperty, scope: Scope, arg) { + const {object} = arg; + Object.assign(object, evaluate(node.argument, scope, arg)); }, ImportDeclaration(node: types.ImportDeclaration, scope: Scope, arg) { let defaultImport: string = ""; // default import object diff --git a/test/ComputedProperties.test.ts b/test/ComputedProperties.test.ts new file mode 100644 index 00000000..4d1c9bf4 --- /dev/null +++ b/test/ComputedProperties.test.ts @@ -0,0 +1,71 @@ +import test from "ava"; + +import vm from "../src/vm"; + +test("ComputedProperties-1", t => { + const sandbox: any = vm.createContext({}); + + const obj: any = vm.runInContext( + ` +const name = "axetroy" +const obj = { + ["@" + name]: 21 +} + +module.exports = obj; + `, + sandbox + ); + + t.deepEqual(obj, { + "@axetroy": 21 + }); +}); + +test("ComputedProperties-2", t => { + const sandbox: any = vm.createContext({}); + + const obj: any = vm.runInContext( + ` +var obj = { + ["x" + foo]: "heh", + ["y" + bar]: "noo", + foo: "foo", + bar: "bar" +}; + +module.exports = obj; + `, + sandbox + ); + + t.deepEqual(obj, { + xfoo: "heh", + ybar: "noo", + foo: "foo", + bar: "bar" + }); +}); + +test("ComputedProperties-3", t => { + const sandbox: any = vm.createContext({}); + + const obj: any = vm.runInContext( + ` +var obj = { + foo: "foo", + get ["x" + foo](){ + return this.foo + } +}; + +module.exports = obj; + `, + sandbox + ); + + t.deepEqual(obj.xfoo, "foo"); + t.throws(function() { + obj.xfoo = 123; // it will throw + }); +});