diff --git a/src/com/google/javascript/jscomp/Es6RewriteDestructuring.java b/src/com/google/javascript/jscomp/Es6RewriteDestructuring.java index 67ba80f06e3..c82486a8078 100644 --- a/src/com/google/javascript/jscomp/Es6RewriteDestructuring.java +++ b/src/com/google/javascript/jscomp/Es6RewriteDestructuring.java @@ -53,6 +53,9 @@ public void hotSwapScript(Node scriptRoot, Node originalRoot) { @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { switch (n.getToken()) { + case FUNCTION: + visitFunction(t, n); + break; case PARAM_LIST: visitParamList(t, n, parent); break; @@ -82,6 +85,19 @@ public void visit(NodeTraversal t, Node n, Node parent) { } } + /** + * If the function is an arrow function, wrap the body in a block if it is not already a block. + */ + private void visitFunction(NodeTraversal t, Node function) { + Node body = function.getLastChild(); + if (!body.isNormalBlock()) { + body.detach(); + Node replacement = IR.block(IR.returnNode(body)).useSourceInfoIfMissingFromForTree(body); + function.addChildToBack(replacement); + t.reportCodeChange(); + } + } + /** * Processes trailing default and rest parameters. */ @@ -203,7 +219,8 @@ private void visitForOf(Node node) { } private void visitObjectPattern(NodeTraversal t, Node objectPattern, Node parent) { - Node rhs, nodeToDetach; + Node rhs; + Node nodeToDetach; if (NodeUtil.isNameDeclaration(parent) && !NodeUtil.isEnhancedFor(parent.getParent())) { rhs = objectPattern.getNext(); nodeToDetach = parent; @@ -244,7 +261,8 @@ private void visitObjectPattern(NodeTraversal t, Node objectPattern, Node parent for (Node child = objectPattern.getFirstChild(), next; child != null; child = next) { next = child.getNext(); - Node newLHS, newRHS; + Node newLHS; + Node newRHS; if (child.isStringKey()) { if (!child.hasChildren()) { // converting shorthand Node name = IR.name(child.getString()); @@ -314,35 +332,35 @@ private void visitObjectPattern(NodeTraversal t, Node objectPattern, Node parent } private void visitArrayPattern(NodeTraversal t, Node arrayPattern, Node parent) { - Node rhs, nodeToDetach; if (NodeUtil.isNameDeclaration(parent) && !NodeUtil.isEnhancedFor(parent.getParent())) { - rhs = arrayPattern.getNext(); - nodeToDetach = parent; + replaceArrayPattern(t, arrayPattern, arrayPattern.getNext(), parent, parent); } else if (parent.isAssign()) { - rhs = arrayPattern.getNext(); - nodeToDetach = parent.getParent(); - Preconditions.checkState(nodeToDetach.isExprResult()); + if (parent.getParent().isExprResult()) { + replaceArrayPattern(t, arrayPattern, arrayPattern.getNext(), parent, parent.getParent()); + } else { + wrapAssignmentInCallToArrow(t, parent); + } } else if (parent.isArrayPattern() || parent.isRest() || parent.isDefaultValue() || parent.isStringKey()) { // This is a nested array pattern. Don't do anything now; we'll visit it // after visiting the parent. - return; } else if (NodeUtil.isEnhancedFor(parent) || NodeUtil.isEnhancedFor(parent.getParent())) { visitDestructuringPatternInEnhancedFor(arrayPattern); - return; } else if (parent.isCatch()) { visitDestructuringPatternInCatch(arrayPattern); - return; } else { throw new IllegalStateException("Unexpected ARRAY_PATTERN parent: " + parent); } + } - // Convert 'var [x, y] = rhs' to: - // var temp = $jscomp.makeIterator(rhs); - // var x = temp.next().value; - // var y = temp.next().value; + /** + * Convert 'var [x, y] = rhs' to: var temp = $jscomp.makeIterator(rhs); var x = temp.next().value; + * var y = temp.next().value; + */ + private void replaceArrayPattern( + NodeTraversal t, Node arrayPattern, Node rhs, Node parent, Node nodeToDetach) { String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); Node tempDecl = IR.var( IR.name(tempVarName), @@ -361,7 +379,8 @@ private void visitArrayPattern(NodeTraversal t, Node arrayPattern, Node parent) continue; } - Node newLHS, newRHS; + Node newLHS; + Node newRHS; if (child.isDefaultValue()) { // [x = defaultValue] = rhs; // becomes @@ -423,6 +442,32 @@ private void visitArrayPattern(NodeTraversal t, Node arrayPattern, Node parent) t.reportCodeChange(); } + /** + * Convert the assignment '[x, y] = rhs' (used as an expression and not an expr result) to: (() => + * { var temp = $jscomp.makeIterator(rhs); var x = temp.next().value; var y = temp.next().value; + * return temp; }) + */ + private void wrapAssignmentInCallToArrow(NodeTraversal t, Node assignment) { + String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); + Node rhs = assignment.getLastChild().detach(); + Node newAssignment = IR.let(IR.name(tempVarName), rhs); + Node replacementExpr = IR.assign(assignment.getFirstChild().detach(), IR.name(tempVarName)); + Node exprResult = IR.exprResult(replacementExpr); + Node returnNode = IR.returnNode(IR.name(tempVarName)); + Node block = IR.block(newAssignment, exprResult, returnNode); + Node call = IR.call(IR.arrowFunction(IR.name(""), IR.paramList(), block)); + call.useSourceInfoIfMissingFromForTree(assignment); + call.putBooleanProp(Node.FREE_CALL, true); + assignment.getParent().replaceChild(assignment, call); + NodeUtil.markNewScopesChanged(call, compiler); + replaceArrayPattern( + t, + replacementExpr.getFirstChild(), + replacementExpr.getLastChild(), + replacementExpr, + exprResult); + } + private void visitDestructuringPatternInEnhancedFor(Node pattern) { Preconditions.checkArgument(pattern.isDestructuringPattern()); String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); diff --git a/src/com/google/javascript/jscomp/TranspilationPasses.java b/src/com/google/javascript/jscomp/TranspilationPasses.java index 02200655d63..50afa09f672 100644 --- a/src/com/google/javascript/jscomp/TranspilationPasses.java +++ b/src/com/google/javascript/jscomp/TranspilationPasses.java @@ -44,10 +44,10 @@ public static void addEs2017Passes(List passes) { public static void addEs6EarlyPasses(List passes) { passes.add(es6SuperCheck); passes.add(es6ConvertSuper); - passes.add(es6RewriteArrowFunction); passes.add(es6RenameVariablesInParamLists); passes.add(es6SplitVariableDeclarations); passes.add(es6RewriteDestructuring); + passes.add(es6RewriteArrowFunction); } /** diff --git a/test/com/google/javascript/jscomp/Es6RewriteDestructuringTest.java b/test/com/google/javascript/jscomp/Es6RewriteDestructuringTest.java index 4f3d232120d..57696ad4292 100644 --- a/test/com/google/javascript/jscomp/Es6RewriteDestructuringTest.java +++ b/test/com/google/javascript/jscomp/Es6RewriteDestructuringTest.java @@ -638,6 +638,139 @@ public void testTypeCheck_inlineAnnotations() { TYPE_MISMATCH_WARNING); } + public void testDestructuringNotInExprResult() { + test( + LINE_JOINER.join("var x, a, b;", "x = ([a,b] = [1,2])"), + LINE_JOINER.join( + "var x,a,b;", + "x = (()=>{", + " let $jscomp$destructuring$var0 = [1,2];", + " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", + " a = $jscomp$destructuring$var1.next().value;", + " b = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + "})();")); + + test( + LINE_JOINER.join( + "var foo = function () {", "var x, a, b;", "x = ([a,b] = [1,2]);", "}", "foo();"), + LINE_JOINER.join( + "var foo = function () {", + " var x, a, b;", + " x = (()=>{", + " let $jscomp$destructuring$var0 = [1,2];", + " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", + " a = $jscomp$destructuring$var1.next().value;", + " b = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + " })();", + "}", + "foo();")); + + test( + LINE_JOINER.join("var prefix;", "for (;;[, prefix] = /\\.?([^.]+)$/.exec(prefix)){", "}"), + LINE_JOINER.join( + "var prefix;", + "for (;;(() => {", + " let $jscomp$destructuring$var0 = /\\.?([^.]+)$/.exec(prefix)", + " var $jscomp$destructuring$var1 = ", + "$jscomp.makeIterator($jscomp$destructuring$var0);", + " $jscomp$destructuring$var1.next();", + " prefix = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + " })()){", + "}")); + + test( + LINE_JOINER.join( + "var prefix;", + "for (;;[, prefix] = /\\.?([^.]+)$/.exec(prefix)){", + " console.log(prefix);", + "}"), + LINE_JOINER.join( + "var prefix;", + "for (;;(() => {", + " let $jscomp$destructuring$var0 = /\\.?([^.]+)$/.exec(prefix)", + " var $jscomp$destructuring$var1 = ", + "$jscomp.makeIterator($jscomp$destructuring$var0);", + " $jscomp$destructuring$var1.next();", + " prefix = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + " })()){", + " console.log(prefix);", + "}")); + + test( + LINE_JOINER.join("for (var x = 1; x < 3; [x,] = [3,4]){", " console.log(x);", "}"), + LINE_JOINER.join( + "for (var x = 1; x < 3; (()=>{", + " let $jscomp$destructuring$var0 = [3,4]", + " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", + " x = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + " })()){", + "console.log(x);", + "}")); + } + + public void testNestedDestructuring() { + test( + "var [[x]] = [[1]];", + LINE_JOINER.join( + "var $jscomp$destructuring$var0 = $jscomp.makeIterator([[1]]);", + "var $jscomp$destructuring$var1 = ", + "$jscomp.makeIterator($jscomp$destructuring$var0.next().value);", + "var x = $jscomp$destructuring$var1.next().value;")); + + test( + "var [[x,y],[z]] = [[1,2],[3]];", + LINE_JOINER.join( + "var $jscomp$destructuring$var0 = $jscomp.makeIterator([[1,2],[3]]);", + "var $jscomp$destructuring$var1 = ", + "$jscomp.makeIterator($jscomp$destructuring$var0.next().value);", + "var x = $jscomp$destructuring$var1.next().value;", + "var y = $jscomp$destructuring$var1.next().value;", + "var $jscomp$destructuring$var2 = ", + "$jscomp.makeIterator($jscomp$destructuring$var0.next().value);", + "var z = $jscomp$destructuring$var2.next().value;")); + + test( + "var [[x,y],z] = [[1,2],3];", + LINE_JOINER.join( + "var $jscomp$destructuring$var0 = $jscomp.makeIterator([[1,2],3]);", + "var $jscomp$destructuring$var1 = ", + "$jscomp.makeIterator($jscomp$destructuring$var0.next().value);", + "var x = $jscomp$destructuring$var1.next().value;", + "var y = $jscomp$destructuring$var1.next().value;", + "var z = $jscomp$destructuring$var0.next().value;")); + } + + public void testTryCatch() { + test( + LINE_JOINER.join("var x = 1;", "try {", " throw [];", "} catch ([x]) {}"), + LINE_JOINER.join( + "var x = 1;", + "try {", + " throw [];", + "} catch ($jscomp$destructuring$var0) {", + " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", + " let x = $jscomp$destructuring$var1.next().value;", + "}")); + + test( + LINE_JOINER.join("var x = 1;", "try {", " throw [[]];", "} catch ([[x]]) {}"), + LINE_JOINER.join( + "var x = 1;", + "try {", + " throw [[]];", + "} catch ($jscomp$destructuring$var0) {", + " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", + " var $jscomp$destructuring$var2 = ", + "$jscomp.makeIterator($jscomp$destructuring$var1.next().value);", + " let x = $jscomp$destructuring$var2.next().value;", + "}")); + } + @Override protected Compiler createCompiler() { return new NoninjectingCompiler(); diff --git a/test/com/google/javascript/jscomp/MultiPassTest.java b/test/com/google/javascript/jscomp/MultiPassTest.java index 3e97f465540..9803830a179 100644 --- a/test/com/google/javascript/jscomp/MultiPassTest.java +++ b/test/com/google/javascript/jscomp/MultiPassTest.java @@ -17,6 +17,7 @@ package com.google.javascript.jscomp; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; +import com.google.javascript.jscomp.parsing.parser.FeatureSet; import com.google.javascript.rhino.Node; import java.util.LinkedList; import java.util.List; @@ -55,6 +56,11 @@ protected CompilerPass create(AbstractCompiler compiler) { return phaseopt; } + @Override + protected int getNumRepetitions() { + return 1; + } + @Override protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); @@ -140,6 +146,121 @@ public void testTwoOptimLoopsNoCrash() { test("var x = '';", ""); } + public void testDestructuringAndArrowFunction() { + setLanguage(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5); + disableNormalize(); + allowExternsChanges(); + + passes = new LinkedList<>(); + addRenameVariablesInParamListsPass(); + addSplitVariableDeclarationsPass(); + addDestructuringPass(); + addArrowFunctionPass(); + + test( + LINE_JOINER.join( + "var foo = (x,y) => x===y;", + "var f = ({key: value}) => foo('v', value);", + "f({key: 'v'})"), + LINE_JOINER.join( + "var foo = function(x,y) {return x===y;};", + "var f = function ($jscomp$destructuring$var0) {", + " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;", + " var value = $jscomp$destructuring$var1.key;", + " return foo('v', value);", + "};", + "f({key:'v'})")); + + test( + LINE_JOINER.join("var x, a, b;", "x = ([a,b] = [1,2])"), + LINE_JOINER.join( + "var x, a, b;", + "x = function () {", + " let $jscomp$destructuring$var0 = [1,2];", + " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", + " a = $jscomp$destructuring$var1.next().value;", + " b = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + "} ();")); + + test( + LINE_JOINER.join("var x, a, b;", "x = (() => {console.log(); return [a,b] = [1,2];})()"), + LINE_JOINER.join( + "var x, a, b;", + "x = function () {", + " console.log();", + " return function () {", + " let $jscomp$destructuring$var0 = [1,2];", + " var $jscomp$destructuring$var1 = ", + "$jscomp.makeIterator($jscomp$destructuring$var0);", + " a = $jscomp$destructuring$var1.next().value;", + " b = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + " } ();", + "} ();")); + + test( + LINE_JOINER.join( + "var foo = function () {", "var x, a, b;", "x = ([a,b] = [1,2]);", "}", "foo();"), + LINE_JOINER.join( + "var foo = function () {", + "var x, a, b;", + " x = function () {", + " let $jscomp$destructuring$var0 = [1,2];", + " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", + " a = $jscomp$destructuring$var1.next().value;", + " b = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + " } ();", + "}", + "foo();")); + + test( + LINE_JOINER.join("var prefix;", "for (;;[, prefix] = /\\.?([^.]+)$/.exec(prefix)){", "}"), + LINE_JOINER.join( + "var prefix;", + "for (;;function () {", + " let $jscomp$destructuring$var0 = /\\.?([^.]+)$/.exec(prefix)", + " var $jscomp$destructuring$var1 = ", + "$jscomp.makeIterator($jscomp$destructuring$var0);", + " $jscomp$destructuring$var1.next();", + " prefix = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + " }()){", + "}")); + + test( + LINE_JOINER.join( + "var prefix;", + "for (;;[, prefix] = /\\.?([^.]+)$/.exec(prefix)){", + " console.log(prefix);", + "}"), + LINE_JOINER.join( + "var prefix;", + "for (;;function () {", + " let $jscomp$destructuring$var0 = /\\.?([^.]+)$/.exec(prefix)", + " var $jscomp$destructuring$var1 = ", + "$jscomp.makeIterator($jscomp$destructuring$var0);", + " $jscomp$destructuring$var1.next();", + " prefix = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + " } ()){", + " console.log(prefix);", + "}")); + + test( + LINE_JOINER.join("for (var x = 1; x < 3; [x,] = [3,4]){", " console.log(x);", "}"), + LINE_JOINER.join( + "for (var x = 1; x < 3; function () {", + " let $jscomp$destructuring$var0 = [3,4]", + " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", + " x = $jscomp$destructuring$var1.next().value;", + " return $jscomp$destructuring$var0;", + " } ()){", + "console.log(x);", + "}")); + } + private void addCollapseObjectLiterals() { passes.add( new PassFactory("collapseObjectLiterals", false) { @@ -238,4 +359,69 @@ public void process(Node externs, Node root) { } }); } + + private void addDestructuringPass() { + passes.add( + new PassFactory("destructuringPass", true) { + @Override + protected CompilerPass create(final AbstractCompiler compiler) { + return new Es6RewriteDestructuring(compiler); + } + + @Override + protected FeatureSet featureSet() { + return FeatureSet.ES8_MODULES; + } + }); + } + + private void addArrowFunctionPass() { + passes.add( + new PassFactory("arrowFunctionPass", true) { + @Override + protected CompilerPass create(final AbstractCompiler compiler) { + return new Es6RewriteArrowFunction(compiler); + } + + @Override + protected FeatureSet featureSet() { + return FeatureSet.ES8_MODULES; + } + }); + } + + private void addSplitVariableDeclarationsPass() { + passes.add( + new PassFactory("splitVariableDeclarationsPass", true) { + @Override + protected CompilerPass create(final AbstractCompiler compiler) { + return new Es6SplitVariableDeclarations(compiler); + } + + @Override + protected FeatureSet featureSet() { + return FeatureSet.ES8_MODULES; + } + }); + } + + private void addRenameVariablesInParamListsPass() { + passes.add( + new PassFactory("renameVariablesInParamListsPass", true) { + @Override + protected CompilerPass create(final AbstractCompiler compiler) { + return new Es6RenameVariablesInParamLists(compiler); + } + + @Override + protected FeatureSet featureSet() { + return FeatureSet.ES8_MODULES; + } + }); + } + + @Override + protected Compiler createCompiler() { + return new NoninjectingCompiler(); + } } diff --git a/test/com/google/javascript/jscomp/runtime_tests/mixed_destructuring_test.js b/test/com/google/javascript/jscomp/runtime_tests/mixed_destructuring_test.js index b4ceee6701c..95a211bdeda 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/mixed_destructuring_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/mixed_destructuring_test.js @@ -65,3 +65,21 @@ function testFunction4() { } f({x: [1]}); } + +function testDestructuringArray() { + var x, a, b; + x = ([a, b] = [1, 2]); + assertEquals(a, 1); + assertEquals(b, 2); + assertEquals(x[0], 1); + assertEquals(x[1], 2); + + let id = 0; + let getNextId = () => (id++); + let w, y, z; + z = ([w, y] = [getNextId(), getNextId()]); + assertEquals(w, 0); + assertEquals(y, 1); + assertEquals(z[0], 0); + assertEquals(z[1], 1); +}