/* * Copyright 2008 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.javascript.jscomp.VariableReferenceCheck.DECLARATION_NOT_DIRECTLY_IN_BLOCK; import static com.google.javascript.jscomp.VariableReferenceCheck.EARLY_REFERENCE; import static com.google.javascript.jscomp.VariableReferenceCheck.EARLY_REFERENCE_ERROR; import static com.google.javascript.jscomp.VariableReferenceCheck.REASSIGNED_CONSTANT; import static com.google.javascript.jscomp.VariableReferenceCheck.REDECLARED_VARIABLE; import static com.google.javascript.jscomp.VariableReferenceCheck.REDECLARED_VARIABLE_ERROR; import static com.google.javascript.jscomp.VariableReferenceCheck.UNUSED_LOCAL_ASSIGNMENT; /** * Test that warnings are generated in appropriate cases and appropriate * cases only by VariableReferenceCheck * */ public final class VariableReferenceCheckTest extends Es6CompilerTestCase { private static final String LET_RUN = "let a = 1; let b = 2; let c = a + b, d = c;"; private static final String VARIABLE_RUN = "var a = 1; var b = 2; var c = a + b, d = c;"; private boolean enableUnusedLocalAssignmentCheck; @Override public CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); if (enableUnusedLocalAssignmentCheck) { options.setWarningLevel(DiagnosticGroups.UNUSED_LOCAL_VARIABLE, CheckLevel.WARNING); } return options; } @Override protected CompilerPass getProcessor(Compiler compiler) { // Treats bad reads as errors, and reports bad write warnings. return new VariableReferenceCheck(compiler); } @Override public void setUp() throws Exception { super.setUp(); enableUnusedLocalAssignmentCheck = false; } public void testDoubleTryCatch() { testSame( LINE_JOINER.join( "function g() {", " return f;", "", " function f() {", " try {", " } catch (e) {", " alert(e);", " }", " try {", " } catch (e) {", " alert(e);", " }", " }", "}")); } public void testCorrectCode() { assertNoWarning("function foo(d) { (function() { d.foo(); }); d.bar(); } "); assertNoWarning("function foo() { bar(); } function bar() { foo(); } "); assertNoWarning("function f(d) { d = 3; }"); assertNoWarning(VARIABLE_RUN); assertNoWarning("if (a) { var x; }"); assertNoWarning("function f() { " + VARIABLE_RUN + "}"); assertNoWarningEs6(LET_RUN); assertNoWarningEs6("function f() { " + LET_RUN + "}"); assertNoWarningEs6("try { let e; } catch (e) { let x; }"); } public void testCorrectShadowing() { assertNoWarning(VARIABLE_RUN + "function f() { " + VARIABLE_RUN + "}"); } public void testCorrectRedeclare() { assertNoWarning( "function f() { if (1) { var a = 2; } else { var a = 3; } }"); } public void testCorrectRecursion() { assertNoWarning("function f() { var x = function() { x(); }; }"); } public void testCorrectCatch() { assertNoWarning("function f() { try { var x = 2; } catch (x) {} }"); assertNoWarning("function f(e) { e = 3; try {} catch (e) {} }"); } public void testRedeclare() { // Only test local scope since global scope is covered elsewhere assertRedeclare("function f() { var a = 2; var a = 3; }"); assertRedeclare("function f(a) { var a = 2; }"); assertRedeclare("function f(a) { if (!a) var a = 6; }"); // NOTE: We decided to not give warnings to the following cases. The function won't be // overwritten at runtime anyway. assertNoWarning("function f() { var f = 1; }"); assertNoWarningEs6("function f() { let f = 1; }"); } public void testIssue166a() { testError( "try { throw 1 } catch(e) { /** @suppress {duplicate} */ var e=2 }", REDECLARED_VARIABLE_ERROR); } public void testIssue166b() { testError( "function a() { try { throw 1 } catch(e) { /** @suppress {duplicate} */ var e=2 } };", REDECLARED_VARIABLE_ERROR); } public void testIssue166c() { testError( "var e = 0; try { throw 1 } catch(e) { /** @suppress {duplicate} */ var e=2 }", REDECLARED_VARIABLE_ERROR); } public void testIssue166d() { testError( LINE_JOINER.join( "function a() {", " var e = 0; try { throw 1 } catch(e) {", " /** @suppress {duplicate} */ var e = 2;", " }", "};"), REDECLARED_VARIABLE_ERROR); } public void testIssue166e() { testSame("var e = 2; try { throw 1 } catch(e) {}"); } public void testIssue166f() { testSame( LINE_JOINER.join( "function a() {", " var e = 2;", " try { throw 1 } catch(e) {}", "}")); } public void testEarlyReference() { assertUndeclared("function f() { a = 2; var a = 3; }"); } public void testCorrectEarlyReference() { assertNoWarning("var goog = goog || {}"); assertNoWarning("var google = google || window['google'] || {}"); assertNoWarning("function f() { a = 2; } var a = 2;"); } public void testUnreferencedBleedingFunction() { assertNoWarning("var x = function y() {}"); assertNoWarning("var x = function y() {}; var y = 1;"); } public void testReferencedBleedingFunction() { assertNoWarning("var x = function y() { return y(); }"); } public void testDoubleDeclaration() { assertRedeclare("function x(y) { if (true) { var y; } }"); } public void testDoubleDeclaration2() { assertRedeclare("function x() { var y; if (true) { var y; } }"); } public void testHoistedFunction1() { assertNoWarning("f(); function f() {}"); } public void testHoistedFunction2() { assertNoWarning("function g() { f(); function f() {} }"); } public void testNonHoistedFunction() { assertUndeclaredEs6("if (true) { f(); function f() {} }"); } public void testNonHoistedFunction2() { assertNoWarningEs6("if (false) { function f() {} f(); }"); } public void testNonHoistedFunction3() { assertNoWarningEs6("function g() { if (false) { function f() {} f(); }}"); } public void testNonHoistedFunction4() { assertAmbiguousEs6("if (false) { function f() {} } f();"); } public void testNonHoistedFunction5() { assertAmbiguousEs6("function g() { if (false) { function f() {} } f(); }"); } public void testNonHoistedFunction6() { assertUndeclaredEs6("if (false) { f(); function f() {} }"); } public void testNonHoistedFunction7() { assertUndeclaredEs6("function g() { if (false) { f(); function f() {} }}"); } public void testNonHoistedRecursiveFunction1() { assertNoWarningEs6("if (false) { function f() { f(); }}"); } public void testNonHoistedRecursiveFunction2() { assertNoWarningEs6("function g() { if (false) { function f() { f(); }}}"); } public void testNonHoistedRecursiveFunction3() { assertNoWarningEs6("function g() { if (false) { function f() { f(); g(); }}}"); } public void testForOf() { assertEarlyReferenceError("for (let x of []) { console.log(x); let x = 123; }"); assertNoWarningEs6("for (let x of []) { let x; }"); } public void testDestructuringInFor() { testSameEs6("for (let [key, val] of X){}"); testSameEs6("for (let [key, [nestKey, nestVal], val] of X){}"); testSameEs6("var {x: a, y: b} = {x: 1, y: 2}; a++; b++;"); testWarningEs6("a++; var {x: a} = {x: 1};", EARLY_REFERENCE); } public void testNoWarnInExterns1() { // Verify duplicate suppressions are properly recognized. String externs = "var google; /** @suppress {duplicate} */ var google"; String code = ""; testSame(externs, code, null); } public void testNoWarnInExterns2() { // Verify we don't complain about early references in externs String externs = "window; var window;"; String code = ""; testSame(externs, code, null); } public void testUnusedLocalVar() { enableUnusedLocalAssignmentCheck = true; assertUnused("function f() { var a; }"); assertUnused("function f() { var a = 2; }"); assertUnused("function f() { var a; a = 2; }"); } public void testUnusedTypedefInModule() { enableUnusedLocalAssignmentCheck = true; assertUnused("goog.module('m'); var x;"); assertUnusedEs6("goog.module('m'); let x;"); testSame("goog.module('m'); /** @typedef {string} */ var x;"); testSameEs6("goog.module('m'); /** @typedef {string} */ let x;"); } public void testAliasInModule() { enableUnusedLocalAssignmentCheck = true; testSameEs6( LINE_JOINER.join( "goog.module('m');", "const x = goog.require('x');", "const y = x.y;", "/** @type {y} */ var z;", "alert(z);")); } /** * Inside a goog.scope, don't warn because the alias might be used in a type annotation. */ public void testUnusedLocalVarInGoogScope() { enableUnusedLocalAssignmentCheck = true; testSame("goog.scope(function f() { var a; });"); testSame("goog.scope(function f() { /** @typedef {some.long.name} */ var a; });"); testSame("goog.scope(function f() { var a = some.long.name; });"); } public void testUnusedLocalLet() { enableUnusedLocalAssignmentCheck = true; assertUnusedEs6("function f() { let a; }"); assertUnusedEs6("function f() { let a = 2; }"); assertUnusedEs6("function f() { let a; a = 2; }"); } public void testUnusedLocalConst() { enableUnusedLocalAssignmentCheck = true; assertUnusedEs6("function f() { const a = 2; }"); } public void testUnusedLocalArgNoWarning() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("function f(a) {}"); } public void testUnusedGlobalNoWarning() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("var a = 2;"); } public void testUnusedGlobalInBlockNoWarning() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("if (true) { var a = 2; }"); } public void testUnusedLocalInBlock() { enableUnusedLocalAssignmentCheck = true; assertUnusedEs6("if (true) { let a = 2; }"); assertUnusedEs6("if (true) { const a = 2; }"); } public void testUnusedAssignedInInnerFunction() { enableUnusedLocalAssignmentCheck = true; assertUnused("function f() { var x = 1; function g() { x = 2; } }"); } public void testIncrementDecrementResultUsed() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("function f() { var x = 5; while (x-- > 0) {} }"); assertNoWarning("function f() { var x = -5; while (x++ < 0) {} }"); assertNoWarning("function f() { var x = 5; while (--x > 0) {} }"); assertNoWarning("function f() { var x = -5; while (++x < 0) {} }"); } public void testUsedInInnerFunction() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("function f() { var x = 1; function g() { use(x); } }"); } public void testUsedInShorthandObjLit() { enableUnusedLocalAssignmentCheck = true; assertUndeclaredEs6("var z = {x}; z(); var x;"); testSameEs6("var {x} = foo();"); testSameEs6("var {x} = {};"); // TODO(moz): Maybe add a warning for this case testSameEs6("function f() { var x = 1; return {x}; }"); } public void testUnusedCatch() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("function f() { try {} catch (x) {} }"); } public void testIncrementCountsAsUse() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("var a = 2; var b = []; b[a++] = 1;"); } public void testForIn() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("for (var prop in obj) {}"); assertNoWarning("for (prop in obj) {}"); assertNoWarning("var prop; for (prop in obj) {}"); } public void testUnusedCompoundAssign() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("var x = 0; function f() { return x += 1; }"); assertNoWarningEs6("var x = 0; var f = () => x += 1;"); assertNoWarningEs6( LINE_JOINER.join( "function f(elapsed) {", " let fakeMs = 0;", " stubs.replace(goog, 'now', () => fakeMs += elapsed);", "}")); assertNoWarningEs6( LINE_JOINER.join( "function f(elapsed) {", " let fakeMs = 0;", " stubs.replace(goog, 'now', () => fakeMs -= elapsed);", "}")); } public void testChainedAssign() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("var a, b = 0, c; a = b = c; alert(a);"); } public void testGoogModule() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("goog.module('example'); var X = 3; use(X);"); assertUnused("goog.module('example'); var X = 3;"); } public void testGoogModule_bundled() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("goog.loadModule(function(exports) { 'use strict';" + "goog.module('example'); var X = 3; use(X);" + "return exports; });"); assertUnused("goog.loadModule(function(exports) { 'use strict';" + "goog.module('example'); var X = 3;" + "return exports; });"); } public void testGoogModule_destructuring() { enableUnusedLocalAssignmentCheck = true; assertNoWarningEs6("goog.module('example'); var {x} = goog.require('y'); use(x);"); // We could warn here, but it's already caught by the extra require check. assertNoWarningEs6("goog.module('example'); var {x} = goog.require('y');"); } public void testGoogModule_require() { enableUnusedLocalAssignmentCheck = true; assertNoWarning("goog.module('example'); var X = goog.require('foo.X'); use(X);"); // We could warn here, but it's already caught by the extra require check. assertNoWarning("goog.module('example'); var X = goog.require('foo.X');"); } public void testGoogModule_forwardDeclare() { enableUnusedLocalAssignmentCheck = true; assertNoWarning( LINE_JOINER.join( "goog.module('example');", "", "var X = goog.forwardDeclare('foo.X');", "", "/** @type {X} */ var x = 0;", "alert(x);")); assertNoWarning("goog.module('example'); var X = goog.forwardDeclare('foo.X');"); } public void testGoogModule_usedInTypeAnnotation() { enableUnusedLocalAssignmentCheck = true; assertNoWarning( "goog.module('example'); var X = goog.require('foo.X'); /** @type {X} */ var y; use(y);"); } public void testGoogModule_duplicateRequire() { assertRedeclareError( "goog.module('bar'); const X = goog.require('foo.X'); const X = goog.require('foo.X');"); assertRedeclareError( "goog.module('bar'); let X = goog.require('foo.X'); let X = goog.require('foo.X');"); assertRedeclareError( "goog.module('bar'); const X = goog.require('foo.X'); let X = goog.require('foo.X');"); assertRedeclareError( "goog.module('bar'); let X = goog.require('foo.X'); const X = goog.require('foo.X');"); } public void testUndeclaredLet() { assertEarlyReferenceError("if (a) { x = 3; let x;}"); assertEarlyReferenceError(LINE_JOINER.join( "var x = 1;", "if (true) {", " x++;", " let x = 3;", "}")); } public void testUndeclaredConst() { assertEarlyReferenceError("if (a) { x = 3; const x = 3;}"); // For the following, IE 11 gives "Assignment to const", but technically // they are also undeclared references, which get caught in the first place. assertEarlyReferenceError(LINE_JOINER.join( "var x = 1;", "if (true) {", " x++;", " const x = 3;", "}")); assertEarlyReferenceError("a = 1; const a = 0;"); assertEarlyReferenceError("a++; const a = 0;"); } public void testIllegalLetShadowing() { assertRedeclareError("if (a) { let x; var x;}"); assertRedeclareError("if (a) { let x; let x;}"); assertRedeclareError(LINE_JOINER.join( "function f() {", " let x;", " if (a) {", " var x;", " }", "}")); assertNoWarningEs6( LINE_JOINER.join( "function f() {", " if (a) {", " let x;", " }", " var x;", "}")); assertNoWarningEs6( LINE_JOINER.join( "function f() {", " if (a) { let x; }", " if (b) { var x; }", "}")); assertRedeclareError("let x; var x;"); assertRedeclareError("var x; let x;"); assertRedeclareError("let x; let x;"); } public void testDuplicateLetConst() { assertRedeclareError("let x, x;"); assertRedeclareError("const x = 0, x = 0;"); } public void testIllegalBlockScopedEarlyReference() { assertEarlyReferenceError("let x = x"); assertEarlyReferenceError("let [x] = x"); assertEarlyReferenceError("const x = x"); assertEarlyReferenceError("let x = x || 0"); assertEarlyReferenceError("const x = x || 0"); // In the following cases, "x" might not be reachable but we warn anyways assertEarlyReferenceError("let x = expr || x"); assertEarlyReferenceError("const x = expr || x"); assertEarlyReferenceError("X; class X {};"); } public void testIllegalConstShadowing() { assertRedeclareError("if (a) { const x = 3; var x;}"); assertRedeclareError(LINE_JOINER.join( "function f() {", " const x = 3;", " if (a) {", " var x;", " }", "}")); } public void testVarShadowing() { assertRedeclareGlobal("if (a) { var x; var x;}"); assertRedeclareError("if (a) { var x; let x;}"); assertRedeclare("function f() { var x; if (a) { var x; }}"); assertRedeclareError("function f() { if (a) { var x; } let x;}"); assertNoWarningEs6("function f() { var x; if (a) { let x; }}"); assertNoWarningEs6( LINE_JOINER.join( "function f() {", " if (a) { var x; }", " if (b) { let x; }", "}")); } public void testParameterShadowing() { assertRedeclareError("function f(x) { let x; }"); assertRedeclareError("function f(x) { const x = 3; }"); assertRedeclareError("function f(X) { class X {} }"); assertRedeclare("function f(x) { function x() {} }"); assertRedeclare("function f(x) { var x; }"); assertRedeclareEs6("function f(x=3) { var x; }"); assertNoWarningEs6("function f(...x) {}"); assertRedeclareEs6("function f(...x) { var x; }"); assertRedeclareEs6("function f(...x) { function x() {} }"); assertRedeclareEs6("function f(x=3) { function x() {} }"); assertNoWarningEs6("function f(x) { if (true) { let x; } }"); assertNoWarningEs6(LINE_JOINER.join( "function outer(x) {", " function inner() {", " let x = 1;", " }", "}")); assertNoWarning(LINE_JOINER.join( "function outer(x) {", " function inner() {", " var x = 1;", " }", "}")); assertRedeclareEs6("function f({a, b}) { var a = 2 }"); assertRedeclareEs6("function f({a, b}) { if (!a) var a = 6; }"); } public void testReassignedConst() { assertReassign("const a = 0; a = 1;"); assertReassign("const a = 0; a++;"); } public void testLetConstNotDirectlyInBlock() { testSame("if (true) var x = 3;"); testErrorEs6("if (true) let x = 3;", DECLARATION_NOT_DIRECTLY_IN_BLOCK); testErrorEs6("if (true) const x = 3;", DECLARATION_NOT_DIRECTLY_IN_BLOCK); testErrorEs6("if (true) class C {}", DECLARATION_NOT_DIRECTLY_IN_BLOCK); testError("if (true) function f() {}", DECLARATION_NOT_DIRECTLY_IN_BLOCK); } public void testFunctionHoisting() { assertUndeclaredEs6("if (true) { f(); function f() {} }"); } public void testFunctionHoistingRedeclaration1() { String[] js = { "var x;", "function x() {}", }; String message = "Variable x declared more than once. First occurence: input0"; test(js, null, VarCheck.VAR_MULTIPLY_DECLARED_ERROR, null, message); } public void testFunctionHoistingRedeclaration2() { String[] js = { "function x() {}", "var x;", }; String message = "Variable x declared more than once. First occurence: input0"; test(js, null, VarCheck.VAR_MULTIPLY_DECLARED_ERROR, null, message); } public void testArrowFunction() { assertNoWarningEs6("var f = x => { return x+1; };"); assertNoWarningEs6("var odds = [1,2,3,4].filter((n) => n%2 == 1)"); assertRedeclareEs6("var f = x => {var x;}"); assertRedeclareError("var f = x => {let x;}"); } public void testTryCatch() { assertRedeclareError( LINE_JOINER.join( "function f() {", " try {", " let e = 0;", " if (true) {", " let e = 1;", " }", " } catch (e) {", " let e;", " }", "}")); assertRedeclareError( LINE_JOINER.join( "function f() {", " try {", " let e = 0;", " if (true) {", " let e = 1;", " }", " } catch (e) {", " var e;", " }", "}")); assertRedeclareError( LINE_JOINER.join( "function f() {", " try {", " let e = 0;", " if (true) {", " let e = 1;", " }", " } catch (e) {", " function e() {", " var e;", " }", " }", "}")); } public void testClass() { assertNoWarningEs6("class A { f() { return 1729; } }"); } public void testRedeclareClassName() { assertNoWarningEs6("var Clazz = class Foo {}; var Foo = 3;"); } public void testClassExtend() { assertNoWarningEs6("class A {} class C extends A {} C = class extends A {}"); } public void testArrayPattern() { assertNoWarningEs6("var [a] = [1];"); assertNoWarningEs6("var [a, b] = [1, 2];"); assertUndeclaredEs6("alert(a); var [a] = [1];"); assertUndeclaredEs6("alert(b); var [a, b] = [1, 2];"); assertUndeclaredEs6("[a] = [1]; var a;"); assertUndeclaredEs6("[a, b] = [1]; var b;"); } public void testArrayPattern_defaultValue() { assertNoWarningEs6("var [a = 1] = [2];"); assertNoWarningEs6("var [a = 1] = [];"); assertUndeclaredEs6("alert(a); var [a = 1] = [2];"); assertUndeclaredEs6("alert(a); var [a = 1] = [];"); assertUndeclaredEs6("alert(a); var [a = b] = [1];"); assertUndeclaredEs6("alert(a); var [a = b] = [];"); } public void testObjectPattern() { assertNoWarningEs6("var {a: b} = {a: 1};"); assertNoWarningEs6("var {a: b} = {};"); assertNoWarningEs6("var {a} = {a: 1};"); // 'a' is not declared at all, so the 'a' passed to alert() references // the global variable 'a', and there is no warning. assertNoWarningEs6("alert(a); var {a: b} = {};"); assertUndeclaredEs6("alert(b); var {a: b} = {a: 1};"); assertUndeclaredEs6("alert(a); var {a} = {a: 1};"); assertUndeclaredEs6("({a: b} = {}); var a, b;"); } public void testObjectPattern_defaultValue() { assertUndeclaredEs6("alert(b); var {a: b = c} = {a: 1};"); assertUndeclaredEs6("alert(b); var c; var {a: b = c} = {a: 1};"); assertUndeclaredEs6("var {a: b = c} = {a: 1}; var c;"); assertUndeclaredEs6("alert(b); var {a: b = c} = {};"); assertUndeclaredEs6("alert(a); var {a = c} = {a: 1};"); assertUndeclaredEs6("alert(a); var {a = c} = {};"); } /** * We can't catch all possible runtime errors but it's useful to have some * basic checks. */ public void testDefaultParam() { assertEarlyReferenceError("function f(x=a) { let a; }"); assertEarlyReferenceError(LINE_JOINER.join( "function f(x=a) { let a; }", "function g(x=1) { var a; }")); assertEarlyReferenceError("function f(x=a) { var a; }"); assertEarlyReferenceError("function f(x=a()) { function a() {} }"); assertEarlyReferenceError("function f(x=[a]) { var a; }"); assertEarlyReferenceError("function f(x=y, y=2) {}"); assertEarlyReferenceError("function f(x=x) {}"); assertEarlyReferenceError("function f([x]=x) {}"); // x within a function isn't referenced at the time the default value for x is evaluated. assertNoWarningEs6("function f(x=()=>x) {}"); assertNoWarningEs6("function f(x=a) {}"); assertNoWarningEs6("function f(x=a) {} var a;"); assertNoWarningEs6("let b; function f(x=b) { var b; }"); assertNoWarningEs6("function f(y = () => x, x = 5) { return y(); }"); assertNoWarningEs6("function f(x = new foo.bar()) {}"); assertNoWarningEs6("var foo = {}; foo.bar = class {}; function f(x = new foo.bar()) {}"); } public void testDestructuring() { testSameEs6(LINE_JOINER.join( "function f() { ", " var obj = {a:1, b:2}; ", " var {a:c, b:d} = obj; ", "}")); testSameEs6(LINE_JOINER.join( "function f() { ", " var obj = {a:1, b:2}; ", " var {a, b} = obj; ", "}")); assertRedeclareEs6(LINE_JOINER.join( "function f() { ", " var obj = {a:1, b:2}; ", " var {a:c, b:d} = obj; ", " var c = b;", "}")); assertUndeclaredEs6(LINE_JOINER.join( "function f() { ", " var {a:c, b:d} = obj;", " var obj = {a:1, b:2};", "}")); assertUndeclaredEs6(LINE_JOINER.join( "function f() { ", " var {a, b} = obj;", " var obj = {a:1, b:2};", "}")); assertUndeclaredEs6(LINE_JOINER.join( "function f() { ", " var e = c;", " var {a:c, b:d} = {a:1, b:2};", "}")); } public void testDestructuringInLoop() { testSameEs6("for (let {length: x} in obj) {}"); testSameEs6("for (let [{length: z}, w] in obj) {}"); } public void testEnhancedForLoopTemporalDeadZone() { assertEarlyReferenceError("for (let x of [x]);"); assertEarlyReferenceError("for (let x in [x]);"); assertEarlyReferenceError("for (const x of [x]);"); testSameEs6("for (var x of [x]);"); testSameEs6("for (let x of [() => x]);"); testSameEs6("let x = 1; for (let y of [x]);"); } /** * Expects the JS to generate one bad-read error. */ private void assertRedeclare(String js) { testWarning(js, REDECLARED_VARIABLE); } private void assertRedeclareEs6(String js) { testWarningEs6(js, REDECLARED_VARIABLE); } private void assertRedeclareError(String js) { testErrorEs6(js, REDECLARED_VARIABLE_ERROR); } private void assertReassign(String js) { testErrorEs6(js, REASSIGNED_CONSTANT); } private void assertRedeclareGlobal(String js) { testError(js, VarCheck.VAR_MULTIPLY_DECLARED_ERROR); } /** * Expects the JS to generate one bad-write warning. */ private void assertUndeclared(String js) { testWarning(js, EARLY_REFERENCE); } /** * Expects the JS to generate one bad-write warning. */ private void assertUndeclaredEs6(String js) { testWarningEs6(js, EARLY_REFERENCE); } private void assertEarlyReferenceError(String js) { testErrorEs6(js, EARLY_REFERENCE_ERROR); } /** * Expects the JS to generate one bad-write warning. */ private void assertAmbiguousEs6(String js) { testSameEs6(js); // In ES6, these are block scoped functions, so no ambiguity. } /** * Expects the JS to generate one unused local error. */ private void assertUnused(String js) { testWarning(js, UNUSED_LOCAL_ASSIGNMENT); } /** * Expects the JS to generate one unused local error. */ private void assertUnusedEs6(String js) { testWarningEs6(js, UNUSED_LOCAL_ASSIGNMENT); } /** * Expects the JS to generate no errors or warnings. */ private void assertNoWarning(String js) { testSame(js); } /** * Expects the JS to generate no errors or warnings. */ private void assertNoWarningEs6(String js) { testSameEs6(js); } }