From 64fe73e20da5a615a73751c648d78c36a74f836d Mon Sep 17 00:00:00 2001 From: vsavkin Date: Tue, 30 Sep 2014 16:25:20 -0400 Subject: [PATCH] feat(transpiler): handle named params --- tools/transpiler/spec/functions_spec.js | 29 ++++- tools/transpiler/src/ast/named_params.js | 16 +++ .../src/ast/object_pattern_binding_element.js | 7 ++ .../src/codegeneration/DartTransformer.js | 2 + .../codegeneration/NamedParamsTransformer.js | 104 ++++++++++++++++++ tools/transpiler/src/dart_writer.js | 18 ++- 6 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 tools/transpiler/src/ast/named_params.js create mode 100644 tools/transpiler/src/ast/object_pattern_binding_element.js create mode 100644 tools/transpiler/src/codegeneration/NamedParamsTransformer.js diff --git a/tools/transpiler/spec/functions_spec.js b/tools/transpiler/spec/functions_spec.js index cd32f2f0aa..fad983e609 100644 --- a/tools/transpiler/spec/functions_spec.js +++ b/tools/transpiler/spec/functions_spec.js @@ -1,13 +1,40 @@ -import {describe, it, expect} from 'test_lib/test_lib'; +import {describe, ddescribe, it, iit, expect} from 'test_lib/test_lib'; function sum(a, b) { return a + b; } + export function main() { describe('functions', function() { it('should work', function() { expect(sum(1, 2)).toBe(3); }); + + describe("named parameters", function() { + it('should pass named params as named params by using identifier keys', function() { + function f(a, {b, c}) {return a + b + c;} + expect(f(1, {b: 2, c: 3})).toBe(6); + }); + + it('should pass named params as a map by using quoted keys', function() { + function f(m) {return m["a"] + m["b"];} + + expect(f({"a": 1, "b": 2})).toBe(3); + }); + + it('should compile initializers', function() { + function f({a=1, b=2}) {return a + b;} + expect(f({a:10})).toBe(12); + }); + + it("should call function with named params without passing any" + + "params by providing an empty object initializer", function() { + function f({a=1, b=2}={}) {return a + b;} + + expect(f({a: 10})).toBe(12); + expect(f()).toBe(3); + }); + }); }); } diff --git a/tools/transpiler/src/ast/named_params.js b/tools/transpiler/src/ast/named_params.js new file mode 100644 index 0000000000..3bbc19a5dd --- /dev/null +++ b/tools/transpiler/src/ast/named_params.js @@ -0,0 +1,16 @@ +import {ParseTree} from 'traceur/src/syntax/trees/ParseTree'; + +export class NamedParams extends ParseTree { + constructor(location, propertyNameAndValues) { + this.location = location; + this.propertyNameAndValues = propertyNameAndValues; + } + + visit(visitor) { + visitor.visitNamedParamsExpression(this); + } + + transform(transformer) { + return this; + } +} diff --git a/tools/transpiler/src/ast/object_pattern_binding_element.js b/tools/transpiler/src/ast/object_pattern_binding_element.js new file mode 100644 index 0000000000..6bcbf67bf2 --- /dev/null +++ b/tools/transpiler/src/ast/object_pattern_binding_element.js @@ -0,0 +1,7 @@ +import {BindingElement} from 'traceur/src/syntax/trees/ParseTrees'; + +export class ObjectPatternBindingElement extends BindingElement { + visit(visitor) { + visitor.visitObjectPatternBindingElement(this); + } +} diff --git a/tools/transpiler/src/codegeneration/DartTransformer.js b/tools/transpiler/src/codegeneration/DartTransformer.js index 9b88bb1660..65aaa773e4 100644 --- a/tools/transpiler/src/codegeneration/DartTransformer.js +++ b/tools/transpiler/src/codegeneration/DartTransformer.js @@ -6,6 +6,7 @@ import {ClassTransformer} from './ClassTransformer'; import {InstanceOfTransformer} from './InstanceOfTransformer'; import {MultiVarTransformer} from './MultiVarTransformer'; import {StrictEqualityTransformer} from './StrictEqualityTransformer'; +import {NamedParamsTransformer} from './NamedParamsTransformer'; /** * Transforms ES6 + annotations to Dart code. @@ -20,6 +21,7 @@ export class DartTransformer extends MultiTransformer { }); }; + append(NamedParamsTransformer); append(MultiVarTransformer); append(InstanceOfTransformer); append(StrictEqualityTransformer); diff --git a/tools/transpiler/src/codegeneration/NamedParamsTransformer.js b/tools/transpiler/src/codegeneration/NamedParamsTransformer.js new file mode 100644 index 0000000000..9dbae8c25c --- /dev/null +++ b/tools/transpiler/src/codegeneration/NamedParamsTransformer.js @@ -0,0 +1,104 @@ +import {ParseTreeTransformer} from 'traceur/src/codegeneration/ParseTreeTransformer'; +import { + BINDING_ELEMENT, + OBJECT_PATTERN, + OBJECT_LITERAL_EXPRESSION +} from 'traceur/src/syntax/trees/ParseTreeType'; + +import {NamedParams} from '../ast/named_params'; +import {ObjectPatternBindingElement} from '../ast/object_pattern_binding_element'; + +/** + * Transforms maps into named parameters: + * + * First, it transform all calls where the last argument is an object literal + * with identifier keys, as follows: + * + * f({a: 1, b: 2}) -> f(a: 1, b: 2) + * + * Second, it removes the empty object initializer from the function definition: + * + * function f({a:1, b:2} = {}){} -> f({a:1, b:2}){} + */ +export class NamedParamsTransformer extends ParseTreeTransformer { + /** + * @param {CallExpression} tree + * @return {ParseTree} + */ + transformCallExpression(tree) { + tree = super.transformCallExpression(tree); + if (this._isLastArgAnNonEmptyObjectLiteral(tree) && + ! this._isLastArgObjectLiteralWithQuotedKeys(tree)) { + this._replaceLastArgWithNamedParams(tree); + } + return tree; + } + + _isLastArgAnNonEmptyObjectLiteral(tree) { + var lastArg = this._last(tree.args.args); + if (!lastArg) return false; + + var pairs = lastArg.propertyNameAndValues; + if (!pairs || pairs.length == 0) return false; + + return true; + } + + _isLastArgObjectLiteralWithQuotedKeys(tree) { + var pairs = this._last(tree.args.args).propertyNameAndValues; + + for (var pair of pairs) { + var key = pair.name.literalToken.value; + if (key.charAt(0) == '"' || key.charAt(0) == "'") return true; + } + + return false; + } + + _replaceLastArgWithNamedParams(tree) { + var args = tree.args.args; + var last = this._last(args); + args[args.length - 1] = new NamedParams(last.location, last.propertyNameAndValues); + } + + /** + * @param {ObjectPattern} tree + * @return {ParseTree} + */ + transformObjectPattern(tree) { + tree = super.transformObjectPattern(tree); + tree.fields = tree.fields.map( + (e) => new ObjectPatternBindingElement(e.location, e.binding, e.initializer)); + return tree; + } + + /** + * @param {FormalParameterList} tree + * @return {ParseTree} + */ + transformFormalParameterList(tree) { + tree = super.transformFormalParameterList(tree); + var last = this._last(tree.parameters); + if (last && this._isObjectPatternWithAnEmptyObjectInit(last.parameter)) { + last.parameter = last.parameter.binding; + } + return tree; + } + + _isObjectPatternWithAnEmptyObjectInit(tree) { + return tree.type === BINDING_ELEMENT && + tree.binding.type === OBJECT_PATTERN && + this._isEmptyObjectInitializer(tree.initializer) + } + + _isEmptyObjectInitializer(initializer) { + return initializer && + initializer.type == OBJECT_LITERAL_EXPRESSION && + initializer.propertyNameAndValues.length == 0; + } + + _last(array) { + if (!array || array.length == 0) return undefined; + return array[array.length - 1]; + } +} diff --git a/tools/transpiler/src/dart_writer.js b/tools/transpiler/src/dart_writer.js index b0124a9d38..34bd765b1c 100644 --- a/tools/transpiler/src/dart_writer.js +++ b/tools/transpiler/src/dart_writer.js @@ -1,7 +1,7 @@ import {CONSTRUCTOR, FROM} from 'traceur/src/syntax/PredefinedName'; -import {EQUAL_EQUAL_EQUAL, OPEN_PAREN, CLOSE_PAREN, IMPORT, SEMI_COLON, STAR, OPEN_CURLY, CLOSE_CURLY, COMMA, AT, EQUAL} from 'traceur/src/syntax/TokenType'; +import {EQUAL_EQUAL_EQUAL, OPEN_PAREN, CLOSE_PAREN, IMPORT, SEMI_COLON, STAR, OPEN_CURLY, CLOSE_CURLY, COMMA, AT, EQUAL, COLON} from 'traceur/src/syntax/TokenType'; -import {ParseTreeWriter as JavaScriptParseTreeWriter} from 'traceur/src/outputgeneration/ParseTreeWriter'; +import {ParseTreeWriter as JavaScriptParseTreeWriter, ObjectLiteralExpression} from 'traceur/src/outputgeneration/ParseTreeWriter'; export class DartTreeWriter extends JavaScriptParseTreeWriter { constructor(moduleName, outputPath) { @@ -123,6 +123,14 @@ export class DartTreeWriter extends JavaScriptParseTreeWriter { // FUNCTION/METHOD ARGUMENTS // - type infront of the arg name visitBindingElement(tree) { + this._visitBindingElement(tree, EQUAL); + } + + visitObjectPatternBindingElement(tree) { + this._visitBindingElement(tree, COLON); + } + + _visitBindingElement(tree, initSeparator) { // TODO(vojta): This is awful, just copy/pasted from Traceur, // we should still clean it up. var typeAnnotation = this.currentParameterTypeAnnotation_; @@ -134,7 +142,7 @@ export class DartTreeWriter extends JavaScriptParseTreeWriter { if (tree.initializer) { this.writeSpace_(); - this.write_(EQUAL); + this.write_(initSeparator); this.writeSpace_(); this.visitAny(tree.initializer); } @@ -266,6 +274,10 @@ export class DartTreeWriter extends JavaScriptParseTreeWriter { this.writeSpace_() } + visitNamedParamsExpression(tree) { + this.writeList_(tree.propertyNameAndValues, COMMA, false); + } + toString() { return "library " + this.libName + ";\n" + super.toString(); }