/* * Copyright 2015 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import static com.google.common.truth.Truth.assertThat; import static com.google.javascript.jscomp.TypeCheck.POSSIBLE_INEXISTENT_PROPERTY; import static com.google.javascript.jscomp.TypeValidator.TYPE_MISMATCH_WARNING; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; public class Es6RewriteDestructuringTest extends CompilerTestCase { @Override public void setUp() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015); disableTypeCheck(); runTypeCheckAfterProcessing = true; } @Override protected int getNumRepetitions() { return 1; } @Override protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); options.setLanguageOut(LanguageMode.ECMASCRIPT3); return options; } @Override protected CompilerPass getProcessor(Compiler compiler) { return new Es6RewriteDestructuring(compiler); } public void testObjectDestructuring() { test( "var {a: b, c: d} = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = foo();", "var b = $jscomp$destructuring$var0.a;", "var d = $jscomp$destructuring$var0.c;")); assertThat(getLastCompiler().injected).isEmpty(); test( "var {a,b} = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = foo();", "var a = $jscomp$destructuring$var0.a;", "var b = $jscomp$destructuring$var0.b;")); test( "let {a,b} = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = foo();", "let a = $jscomp$destructuring$var0.a;", "let b = $jscomp$destructuring$var0.b;")); test( "const {a,b} = foo();", LINE_JOINER.join( "/** @const */ var $jscomp$destructuring$var0 = foo();", "const a = $jscomp$destructuring$var0.a;", "const b = $jscomp$destructuring$var0.b;")); test( "var x; ({a: x} = foo());", LINE_JOINER.join( "var x;", "var $jscomp$destructuring$var0 = foo();", "x = $jscomp$destructuring$var0.a;")); } public void testObjectDestructuringWithInitializer() { test( "var {a : b = 'default'} = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = foo();", "var b = ($jscomp$destructuring$var0.a === undefined) ?", " 'default' :", " $jscomp$destructuring$var0.a")); test( "var {a = 'default'} = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = foo();", "var a = ($jscomp$destructuring$var0.a === undefined) ?", " 'default' :", " $jscomp$destructuring$var0.a")); } public void testObjectDestructuringNested() { test( "var {a: {b}} = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = foo();", "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0.a;", "var b = $jscomp$destructuring$var1.b")); } public void testObjectDestructuringComputedProps() { test( "var {[a]: b} = foo();", "var $jscomp$destructuring$var0 = foo(); var b = $jscomp$destructuring$var0[a];"); test( "({[a]: b} = foo());", "var $jscomp$destructuring$var0 = foo(); b = $jscomp$destructuring$var0[a];"); test( "var {[foo()]: x = 5} = {};", LINE_JOINER.join( "var $jscomp$destructuring$var0 = {};", "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0[foo()];", "var x = $jscomp$destructuring$var1 === undefined ?", " 5 : $jscomp$destructuring$var1")); test( "function f({['KEY']: x}) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0", " var x = $jscomp$destructuring$var1['KEY']", "}")); } // https://github.com/google/closure-compiler/issues/2189 public void testGithubIssue2189() { setExpectParseWarningsThisTest(); test( LINE_JOINER.join( "/**", " * @param {string} a", " * @param {{b: ?<?>}} __1", " */", "function x(a, { b }) {}"), LINE_JOINER.join( "/**", " * @param {string} a", " * @param {{b: ?<?>}} __1", " */", "function x(a, $jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;", " var b=$jscomp$destructuring$var1.b;", "}")); } public void testObjectDestructuringStrangeProperties() { test( "var {5: b} = foo();", "var $jscomp$destructuring$var0 = foo(); var b = $jscomp$destructuring$var0['5']"); test( "var {0.1: b} = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = foo();", "var b = $jscomp$destructuring$var0['0.1']")); test( "var {'str': b} = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = foo();", "var b = $jscomp$destructuring$var0['str']")); } public void testObjectDestructuringFunction() { test( "function f({a: b}) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0", " var b = $jscomp$destructuring$var1.a", "}")); test( "function f({a}) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0", " var a = $jscomp$destructuring$var1.a", "}")); test( "function f({k: {subkey : a}}) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0", " var $jscomp$destructuring$var2 = $jscomp$destructuring$var1.k;", " var a = $jscomp$destructuring$var2.subkey;", "}")); test( "function f({k: [x, y, z]}) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0", " var $jscomp$destructuring$var2 =", " $jscomp.makeIterator($jscomp$destructuring$var1.k);", " var x = $jscomp$destructuring$var2.next().value;", " var y = $jscomp$destructuring$var2.next().value;", " var z = $jscomp$destructuring$var2.next().value;", "}")); assertThat(getLastCompiler().injected).containsExactly("es6/util/makeiterator"); test( "function f({key: x = 5}) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0", " var x = $jscomp$destructuring$var1.key === undefined ?", " 5 : $jscomp$destructuring$var1.key", "}")); test( "function f({[key]: x = 5}) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0", " var $jscomp$destructuring$var2 = $jscomp$destructuring$var1[key]", " var x = $jscomp$destructuring$var2 === undefined ?", " 5 : $jscomp$destructuring$var2", "}")); test( "function f({x = 5}) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0", " var x = $jscomp$destructuring$var1.x === undefined ?", " 5 : $jscomp$destructuring$var1.x", "}")); } public void testObjectDestructuringFunctionJsDoc() { test( "function f(/** {x: number, y: number} */ {x, y}) {}", LINE_JOINER.join( "function f(/** {x: number, y: number} */ $jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;", " var x = $jscomp$destructuring$var1.x;", " var y = $jscomp$destructuring$var1.y;", "}")); } public void testDefaultParametersDestructuring() { test( "function f({a,b} = foo()) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0){", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0===undefined ?", " foo() : $jscomp$destructuring$var0;", " var a = $jscomp$destructuring$var1.a;", " var b = $jscomp$destructuring$var1.b;", "}")); } public void testArrayDestructuring() { test( "var [x,y] = z();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = $jscomp.makeIterator(z());", "var x = $jscomp$destructuring$var0.next().value;", "var y = $jscomp$destructuring$var0.next().value;")); test( "var x,y;\n" + "[x,y] = z();", LINE_JOINER.join( "var x,y;", "var $jscomp$destructuring$var0 = $jscomp.makeIterator(z());", "x = $jscomp$destructuring$var0.next().value;", "y = $jscomp$destructuring$var0.next().value;")); test( "var [a,b] = c();" + "var [x,y] = z();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = $jscomp.makeIterator(c());", "var a = $jscomp$destructuring$var0.next().value;", "var b = $jscomp$destructuring$var0.next().value;", "var $jscomp$destructuring$var1 = $jscomp.makeIterator(z());", "var x = $jscomp$destructuring$var1.next().value;", "var y = $jscomp$destructuring$var1.next().value;")); } public void testArrayDestructuringDefaultValues() { test( "var a; [a=1] = b();", LINE_JOINER.join( "var a;", "var $jscomp$destructuring$var0 = $jscomp.makeIterator(b())", "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0.next().value", "a = ($jscomp$destructuring$var1 === undefined) ?", " 1 :", " $jscomp$destructuring$var1;")); test( "var [a=1] = b();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = $jscomp.makeIterator(b())", "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0.next().value", "var a = ($jscomp$destructuring$var1 === undefined) ?", " 1 :", " $jscomp$destructuring$var1;")); test( "var [a, b=1, c] = d();", LINE_JOINER.join( "var $jscomp$destructuring$var0=$jscomp.makeIterator(d());", "var a = $jscomp$destructuring$var0.next().value;", "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0.next().value;", "var b = ($jscomp$destructuring$var1 === undefined) ?", " 1 :", " $jscomp$destructuring$var1;", "var c=$jscomp$destructuring$var0.next().value")); test( "var a; [[a] = ['b']] = [];", LINE_JOINER.join( "var a;", "var $jscomp$destructuring$var0 = $jscomp.makeIterator([]);", "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0.next().value;", "var $jscomp$destructuring$var2 = $jscomp.makeIterator(", " $jscomp$destructuring$var1 === undefined", " ? ['b']", " : $jscomp$destructuring$var1);", "a = $jscomp$destructuring$var2.next().value")); } public void testArrayDestructuringParam() { test( "function f([x,y]) { use(x); use(y); }", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", " var x = $jscomp$destructuring$var1.next().value;", " var y = $jscomp$destructuring$var1.next().value;", " use(x);", " use(y);", "}")); test( "function f([x, , y]) { use(x); use(y); }", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", " var x = $jscomp$destructuring$var1.next().value;", " $jscomp$destructuring$var1.next();", " var y = $jscomp$destructuring$var1.next().value;", " use(x);", " use(y);", "}")); } public void testArrayDestructuringRest() { test( "let [one, ...others] = f();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = $jscomp.makeIterator(f());", "let one = $jscomp$destructuring$var0.next().value;", "let others = $jscomp.arrayFromIterator($jscomp$destructuring$var0);")); assertThat(getLastCompiler().injected) .containsExactly("es6/util/arrayfromiterator", "es6/util/makeiterator"); test( "function f([first, ...rest]) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", " var first = $jscomp$destructuring$var1.next().value;", " var rest = $jscomp.arrayFromIterator($jscomp$destructuring$var1);", "}")); } public void testRestParamDestructuring() { test( "function f(first, ...[re, st, ...{length: num_left}]) {}", LINE_JOINER.join( "function f(first, ...$jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0);", " var re = $jscomp$destructuring$var1.next().value;", " var st = $jscomp$destructuring$var1.next().value;", " var $jscomp$destructuring$var2 = " + "$jscomp.arrayFromIterator($jscomp$destructuring$var1);", " var num_left = $jscomp$destructuring$var2.length;", "}")); } public void testArrayDestructuringMixedRest() { test( "let [first, ...[re, st, ...{length: num_left}]] = f();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = $jscomp.makeIterator(f());", "let first = $jscomp$destructuring$var0.next().value;", "var $jscomp$destructuring$var1 = " + "$jscomp.makeIterator(" + "$jscomp.arrayFromIterator($jscomp$destructuring$var0));", "let re = $jscomp$destructuring$var1.next().value;", "let st = $jscomp$destructuring$var1.next().value;", "var $jscomp$destructuring$var2 = " + "$jscomp.arrayFromIterator($jscomp$destructuring$var1);", "let num_left = $jscomp$destructuring$var2.length;")); } public void testArrayDestructuringArguments() { test( "function f() { var [x, y] = arguments; }", LINE_JOINER.join( "function f() {", " var $jscomp$destructuring$var0 = $jscomp.makeIterator(arguments);", " var x = $jscomp$destructuring$var0.next().value;", " var y = $jscomp$destructuring$var0.next().value;", "}")); } public void testMixedDestructuring() { test( "var [a,{b,c}] = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = $jscomp.makeIterator(foo());", "var a = $jscomp$destructuring$var0.next().value;", "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0.next().value;", "var b = $jscomp$destructuring$var1.b;", "var c = $jscomp$destructuring$var1.c")); test( "var {a,b:[c,d]} = foo();", LINE_JOINER.join( "var $jscomp$destructuring$var0 = foo();", "var a = $jscomp$destructuring$var0.a;", "var $jscomp$destructuring$var1 = $jscomp.makeIterator($jscomp$destructuring$var0.b);", "var c = $jscomp$destructuring$var1.next().value;", "var d = $jscomp$destructuring$var1.next().value")); } public void testDestructuringForOf() { test( "for ({x} of y) { console.log(x); }", LINE_JOINER.join( "for (var $jscomp$destructuring$var0 of y) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;", " x = $jscomp$destructuring$var1.x;", " console.log(x);", "}")); } public void testDefaultValueInObjectPattern() { test( "function f({x = a()}, y = b()) {}", LINE_JOINER.join( "function f($jscomp$destructuring$var0, y) {", "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;", "var x = $jscomp$destructuring$var1.x === undefined", " ? a() : $jscomp$destructuring$var1.x;", "y = y === undefined ? b() : y", "}")); } public void testDefaultParameters() { enableTypeCheck(); test( "function f(zero, one = 1, two = 2) {}; f(1); f(1,2,3);", LINE_JOINER.join( "function f(zero, one, two) {", " one = (one === undefined) ? 1 : one;", " two = (two === undefined) ? 2 : two;", "};", "f(1); f(1,2,3);")); test( "function f(zero, one = 1, two = 2) {}; f();", LINE_JOINER.join( "function f(zero, one, two) {", " one = (one === undefined) ? 1 : one;", " two = (two === undefined) ? 2 : two;", "}; f();"), null, TypeCheck.WRONG_ARGUMENT_COUNT); } public void testDefaultAndRestParameters() { test( "function f(zero, one = 1, ...two) {}", LINE_JOINER.join( "function f(zero, one, ...two) {", " one = (one === undefined) ? 1 : one;", "}")); test( "function f(/** number= */ x = 5) {}", LINE_JOINER.join( "function f(/** number= */ x) {", " x = (x === undefined) ? 5 : x;", "}")); } public void testDefaultUndefinedParameters() { enableTypeCheck(); test("function f(zero, one=undefined) {}", "function f(zero, one) {}"); test("function f(zero, one=void 42) {}", "function f(zero, one) {}"); test("function f(zero, one=void(42)) {}", "function f(zero, one) {}"); test("function f(zero, one=void '\\x42') {}", "function f(zero, one) {}"); test( "function f(zero, one='undefined') {}", "function f(zero, one) { one = (one === undefined) ? 'undefined' : one; }"); test( "function f(zero, one=void g()) {}", "function f(zero, one) { one = (one === undefined) ? void g() : one; }"); } public void testCatch() { test( "try {} catch ({message}) {}", LINE_JOINER.join( "try {} catch($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;", " let message = $jscomp$destructuring$var1.message", "}")); } public void testTypeCheck() { enableTypeCheck(); test( "/** @param {{x: number}} obj */ function f({x}) {}", LINE_JOINER.join( "/** @param {{x: number}} obj */", "function f(obj) {", " var $jscomp$destructuring$var0 = obj;", " var x = $jscomp$destructuring$var0.x;", "}")); testWarning( LINE_JOINER.join( "/** @param {{x: number}} obj */", "function f({x}) {}", "f({ x: 'str'});"), TYPE_MISMATCH_WARNING); test( LINE_JOINER.join( "/** @param {{x: number}} obj */", "var f = function({x}) {}"), LINE_JOINER.join( "/** @param {{x: number}} obj */", "var f = function(obj) {", " var $jscomp$destructuring$var0 = obj;", " var x = $jscomp$destructuring$var0.x;", "}")); test( LINE_JOINER.join( "/** @param {{x: number}} obj */", "f = function({x}) {}"), LINE_JOINER.join( "/** @param {{x: number}} obj */", "f = function(obj) {", " var $jscomp$destructuring$var0 = obj;", " var x = $jscomp$destructuring$var0.x;", "}")); test( LINE_JOINER.join( "/** @param {{x: number}} obj */", "ns.f = function({x}) {}"), LINE_JOINER.join( "/** @param {{x: number}} obj */", "ns.f = function(obj) {", " var $jscomp$destructuring$var0 = obj;", " var x = $jscomp$destructuring$var0.x;", "}")); test( "ns.f = function({x} = {x: 0}) {};", LINE_JOINER.join( "ns.f = function($jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 =", " $jscomp$destructuring$var0 === undefined ? {x:0} : $jscomp$destructuring$var0;", " var x = $jscomp$destructuring$var1.x", "};")); test( LINE_JOINER.join( "/** @param {{x: number}=} obj */", "ns.f = function({x} = {x: 0}) {};"), LINE_JOINER.join( "/** @param {{x: number}=} obj */", "ns.f = function(obj) {", " var $jscomp$destructuring$var0 = obj===undefined ? {x:0} : obj;", " var x = $jscomp$destructuring$var0.x", "};")); } public void testDestructuringPatternInExterns() { enableTypeCheck(); allowExternsChanges(true); testSame( LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "", "Foo.prototype.bar = function({a}) {};"), "(new Foo).bar({b: 0});", POSSIBLE_INEXISTENT_PROPERTY); } public void testTypeCheck_inlineAnnotations() { enableTypeCheck(); test( "function f(/** {x: number} */ {x}) {}", LINE_JOINER.join( "function f(/** {x: number} */ $jscomp$destructuring$var0) {", " var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;", " var x = $jscomp$destructuring$var1.x;", "}")); testWarning( LINE_JOINER.join( "function f(/** {x: number} */ {x}) {}", "f({ x: 'str'});"), TYPE_MISMATCH_WARNING); } @Override protected Compiler createCompiler() { return new NoninjectingCompiler(); } @Override NoninjectingCompiler getLastCompiler() { return (NoninjectingCompiler) super.getLastCompiler(); } }