/* * Copyright 2009 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 com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Tests for {@link PassFactory}. * * @author nicksantos@google.com (Nick Santos) */ public class IntegrationTest extends IntegrationTestCase { private static final String CLOSURE_BOILERPLATE = "/** @define {boolean} */ var COMPILED = false; var goog = {};" + "goog.exportSymbol = function() {};"; private static final String CLOSURE_COMPILED = "var COMPILED = true; var goog$exportSymbol = function() {};"; public void testConstructorCycle() { CompilerOptions options = createCompilerOptions(); options.checkTypes = true; test(options, "/** @return {function()} */ var AsyncTestCase = function() {};\n" + "/**\n" + " * @constructor\n" + " */ Foo = /** @type {function(new:Foo)} */ (AyncTestCase());", RhinoErrorReporter.PARSE_ERROR); } public void testBug1949424() { CompilerOptions options = createCompilerOptions(); options.collapseProperties = true; options.closurePass = true; test(options, CLOSURE_BOILERPLATE + "goog.provide('FOO'); FOO.bar = 3;", CLOSURE_COMPILED + "var FOO$bar = 3;"); } public void testBug1949424_v2() { CompilerOptions options = createCompilerOptions(); options.collapseProperties = true; options.closurePass = true; test(options, CLOSURE_BOILERPLATE + "goog.provide('FOO.BAR'); FOO.BAR = 3;", CLOSURE_COMPILED + "var FOO$BAR = 3;"); } public void testBug1956277() { CompilerOptions options = createCompilerOptions(); options.collapseProperties = true; options.inlineVariables = true; test(options, "var CONST = {}; CONST.bar = null;" + "function f(url) { CONST.bar = url; }", "var CONST$bar = null; function f(url) { CONST$bar = url; }"); } public void testBug1962380() { CompilerOptions options = createCompilerOptions(); options.collapseProperties = true; options.inlineVariables = true; options.generateExports = true; test(options, CLOSURE_BOILERPLATE + "/** @export */ goog.CONSTANT = 1;" + "var x = goog.CONSTANT;", "(function() {})('goog.CONSTANT', 1);" + "var x = 1;"); } public void testBug2410122() { CompilerOptions options = createCompilerOptions(); options.generateExports = true; options.closurePass = true; test(options, "var goog = {};" + "function F() {}" + "/** @export */ function G() { goog.base(this); } " + "goog.inherits(G, F);", "var goog = {};" + "function F() {}" + "function G() { F.call(this); } " + "goog.inherits(G, F); goog.exportSymbol('G', G);"); } public void testIssue90() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; options.inlineVariables = true; options.removeDeadCode = true; test(options, "var x; x && alert(1);", ""); } public void testClosurePassOff() { CompilerOptions options = createCompilerOptions(); options.closurePass = false; testSame( options, "var goog = {}; goog.require = function(x) {}; goog.require('foo');"); testSame( options, "var goog = {}; goog.getCssName = function(x) {};" + "goog.getCssName('foo');"); } public void testClosurePassOn() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; test( options, "var goog = {}; goog.require = function(x) {}; goog.require('foo');", ProcessClosurePrimitives.MISSING_PROVIDE_ERROR); test( options, "/** @define {boolean} */ var COMPILED = false;" + "var goog = {}; goog.getCssName = function(x) {};" + "goog.getCssName('foo');", "var COMPILED = true;" + "var goog = {}; goog.getCssName = function(x) {};" + "'foo';"); } public void testCssNameCheck() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.checkMissingGetCssNameLevel = CheckLevel.ERROR; options.checkMissingGetCssNameBlacklist = "foo"; test(options, "var x = 'foo';", CheckMissingGetCssName.MISSING_GETCSSNAME); } public void testBug2592659() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.checkTypes = true; options.checkMissingGetCssNameLevel = CheckLevel.WARNING; options.checkMissingGetCssNameBlacklist = "foo"; test(options, "var goog = {};\n" + "/**\n" + " * @param {string} className\n" + " * @param {string=} opt_modifier\n" + " * @return {string}\n" + "*/\n" + "goog.getCssName = function(className, opt_modifier) {}\n" + "var x = goog.getCssName(123, 'a');", TypeValidator.TYPE_MISMATCH_WARNING); } public void testTypedefBeforeOwner1() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; test(options, "goog.provide('foo.Bar.Type');\n" + "goog.provide('foo.Bar');\n" + "/** @typedef {number} */ foo.Bar.Type;\n" + "foo.Bar = function() {};", "var foo = {}; foo.Bar.Type; foo.Bar = function() {};"); } public void testTypedefBeforeOwner2() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.collapseProperties = true; test(options, "goog.provide('foo.Bar.Type');\n" + "goog.provide('foo.Bar');\n" + "/** @typedef {number} */ foo.Bar.Type;\n" + "foo.Bar = function() {};", "var foo$Bar$Type; var foo$Bar = function() {};"); } public void testExportedNames() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.variableRenaming = VariableRenamingPolicy.ALL; test(options, "/** @define {boolean} */ var COMPILED = false;" + "var goog = {}; goog.exportSymbol('b', goog);", "var a = true; var c = {}; c.exportSymbol('b', c);"); test(options, "/** @define {boolean} */ var COMPILED = false;" + "var goog = {}; goog.exportSymbol('a', goog);", "var b = true; var c = {}; c.exportSymbol('a', c);"); } public void testCheckGlobalThisOn() { CompilerOptions options = createCompilerOptions(); options.checkSuspiciousCode = true; options.checkGlobalThisLevel = CheckLevel.ERROR; test(options, "function f() { this.y = 3; }", CheckGlobalThis.GLOBAL_THIS); } public void testSusiciousCodeOff() { CompilerOptions options = createCompilerOptions(); options.checkSuspiciousCode = false; options.checkGlobalThisLevel = CheckLevel.ERROR; test(options, "function f() { this.y = 3; }", CheckGlobalThis.GLOBAL_THIS); } public void testCheckGlobalThisOff() { CompilerOptions options = createCompilerOptions(); options.checkSuspiciousCode = true; options.checkGlobalThisLevel = CheckLevel.OFF; testSame(options, "function f() { this.y = 3; }"); } public void testCheckRequiresAndCheckProvidesOff() { testSame(createCompilerOptions(), new String[] { "/** @constructor */ function Foo() {}", "new Foo();" }); } public void testCheckRequiresOn() { CompilerOptions options = createCompilerOptions(); options.checkRequires = CheckLevel.ERROR; test(options, new String[] { "/** @constructor */ function Foo() {}", "new Foo();" }, CheckRequiresForConstructors.MISSING_REQUIRE_WARNING); } public void testCheckProvidesOn() { CompilerOptions options = createCompilerOptions(); options.checkProvides = CheckLevel.ERROR; test(options, new String[] { "/** @constructor */ function Foo() {}", "new Foo();" }, CheckProvides.MISSING_PROVIDE_WARNING); } public void testGenerateExportsOff() { testSame(createCompilerOptions(), "/** @export */ function f() {}"); } public void testGenerateExportsOn() { CompilerOptions options = createCompilerOptions(); options.generateExports = true; test(options, "/** @export */ function f() {}", "/** @export */ function f() {} goog.exportSymbol('f', f);"); } public void testExportTestFunctionsOff() { testSame(createCompilerOptions(), "function testFoo() {}"); } public void testExportTestFunctionsOn() { CompilerOptions options = createCompilerOptions(); options.exportTestFunctions = true; test(options, "function testFoo() {}", "/** @export */ function testFoo() {}" + "goog.exportSymbol('testFoo', testFoo);"); } public void testExpose() { CompilerOptions options = createCompilerOptions(); CompilationLevel.ADVANCED_OPTIMIZATIONS .setOptionsForCompilationLevel(options); test(options, "var x = {eeny: 1, /** @expose */ meeny: 2};" + "/** @constructor */ var Foo = function() {};" + "/** @expose */ Foo.prototype.miny = 3;" + "Foo.prototype.moe = 4;" + "function moe(a, b) { return a.meeny + b.miny; }" + "window['x'] = x;" + "window['Foo'] = Foo;" + "window['moe'] = moe;", "function a(){}" + "a.prototype.miny=3;" + "window.x={a:1,meeny:2};" + "window.Foo=a;" + "window.moe=function(b,c){" + " return b.meeny+c.miny" + "}"); } public void testCheckSymbolsOff() { CompilerOptions options = createCompilerOptions(); testSame(options, "x = 3;"); } public void testCheckSymbolsOn() { CompilerOptions options = createCompilerOptions(); options.checkSymbols = true; test(options, "x = 3;", VarCheck.UNDEFINED_VAR_ERROR); } public void testCheckReferencesOff() { CompilerOptions options = createCompilerOptions(); testSame(options, "x = 3; var x = 5;"); } public void testCheckReferencesOn() { CompilerOptions options = createCompilerOptions(); options.aggressiveVarCheck = CheckLevel.ERROR; test(options, "x = 3; var x = 5;", VariableReferenceCheck.UNDECLARED_REFERENCE); } public void testInferTypes() { CompilerOptions options = createCompilerOptions(); options.inferTypes = true; options.checkTypes = false; options.closurePass = true; test(options, CLOSURE_BOILERPLATE + "goog.provide('Foo'); /** @enum */ Foo = {a: 3};", TypeCheck.ENUM_NOT_CONSTANT); assertTrue(lastCompiler.getErrorManager().getTypedPercent() == 0); // This does not generate a warning. test(options, "/** @type {number} */ var n = window.name;", "var n = window.name;"); assertTrue(lastCompiler.getErrorManager().getTypedPercent() == 0); } public void testTypeCheckAndInference() { CompilerOptions options = createCompilerOptions(); options.checkTypes = true; test(options, "/** @type {number} */ var n = window.name;", TypeValidator.TYPE_MISMATCH_WARNING); assertTrue(lastCompiler.getErrorManager().getTypedPercent() > 0); } public void testTypeNameParser() { CompilerOptions options = createCompilerOptions(); options.checkTypes = true; test(options, "/** @type {n} */ var n = window.name;", RhinoErrorReporter.TYPE_PARSE_ERROR); } // This tests that the TypedScopeCreator is memoized so that it only creates a // Scope object once for each scope. If, when type inference requests a scope, // it creates a new one, then multiple JSType objects end up getting created // for the same local type, and ambiguate will rename the last statement to // o.a(o.a, o.a), which is bad. public void testMemoizedTypedScopeCreator() { CompilerOptions options = createCompilerOptions(); options.checkTypes = true; options.ambiguateProperties = true; options.propertyRenaming = PropertyRenamingPolicy.ALL_UNQUOTED; test(options, "function someTest() {\n" + " /** @constructor */\n" + " function Foo() { this.instProp = 3; }\n" + " Foo.prototype.protoProp = function(a, b) {};\n" + " /** @constructor\n @extends Foo */\n" + " function Bar() {}\n" + " goog.inherits(Bar, Foo);\n" + " var o = new Bar();\n" + " o.protoProp(o.protoProp, o.instProp);\n" + "}", "function someTest() {\n" + " function Foo() { this.b = 3; }\n" + " function Bar() {}\n" + " Foo.prototype.a = function(a, b) {};\n" + " goog.c(Bar, Foo);\n" + " var o = new Bar();\n" + " o.a(o.a, o.b);\n" + "}"); } public void testCheckTypes() { CompilerOptions options = createCompilerOptions(); options.checkTypes = true; test(options, "var x = x || {}; x.f = function() {}; x.f(3);", TypeCheck.WRONG_ARGUMENT_COUNT); } public void testReplaceCssNames() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.gatherCssNames = true; test(options, "/** @define {boolean} */\n" + "var COMPILED = false;\n" + "goog.setCssNameMapping({'foo':'bar'});\n" + "function getCss() {\n" + " return goog.getCssName('foo');\n" + "}", "var COMPILED = true;\n" + "function getCss() {\n" + " return \"bar\";" + "}"); assertEquals( ImmutableMap.of("foo", new Integer(1)), lastCompiler.getPassConfig().getIntermediateState().cssNames); } public void testRemoveClosureAsserts() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; testSame(options, "var goog = {};" + "goog.asserts.assert(goog);"); options.removeClosureAsserts = true; test(options, "var goog = {};" + "goog.asserts.assert(goog);", "var goog = {};"); } public void testDeprecation() { String code = "/** @deprecated */ function f() { } function g() { f(); }"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.setWarningLevel(DiagnosticGroups.DEPRECATED, CheckLevel.ERROR); testSame(options, code); options.checkTypes = true; test(options, code, CheckAccessControls.DEPRECATED_NAME); } public void testVisibility() { String[] code = { "/** @private */ function f() { }", "function g() { f(); }" }; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.setWarningLevel(DiagnosticGroups.VISIBILITY, CheckLevel.ERROR); testSame(options, code); options.checkTypes = true; test(options, code, CheckAccessControls.BAD_PRIVATE_GLOBAL_ACCESS); } public void testUnreachableCode() { String code = "function f() { return \n 3; }"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.checkUnreachableCode = CheckLevel.ERROR; test(options, code, CheckUnreachableCode.UNREACHABLE_CODE); } public void testMissingReturn() { String code = "/** @return {number} */ function f() { if (f) { return 3; } }"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.checkMissingReturn = CheckLevel.ERROR; testSame(options, code); options.checkTypes = true; test(options, code, CheckMissingReturn.MISSING_RETURN_STATEMENT); } public void testIdGenerators() { String code = "function f() {} f('id');"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.idGenerators = Sets.newHashSet("f"); test(options, code, "function f() {} 'a';"); } public void testOptimizeArgumentsArray() { String code = "function f() { return arguments[0]; }"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.optimizeArgumentsArray = true; String argName = "JSCompiler_OptimizeArgumentsArray_p0"; test(options, code, "function f(" + argName + ") { return " + argName + "; }"); } public void testOptimizeParameters() { String code = "function f(a) { return a; } f(true);"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.optimizeParameters = true; test(options, code, "function f() { var a = true; return a;} f();"); } public void testOptimizeReturns() { String code = "function f(a) { return a; } f(true);"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.optimizeReturns = true; test(options, code, "function f(a) {return;} f(true);"); } public void testRemoveAbstractMethods() { String code = CLOSURE_BOILERPLATE + "var x = {}; x.foo = goog.abstractMethod; x.bar = 3;"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.closurePass = true; options.collapseProperties = true; test(options, code, CLOSURE_COMPILED + " var x$bar = 3;"); } public void testCollapseProperties1() { String code = "var x = {}; x.FOO = 5; x.bar = 3;"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.collapseProperties = true; test(options, code, "var x$FOO = 5; var x$bar = 3;"); } public void testCollapseProperties2() { String code = "var x = {}; x.FOO = 5; x.bar = 3;"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.collapseProperties = true; options.collapseObjectLiterals = true; test(options, code, "var x$FOO = 5; var x$bar = 3;"); } public void testCollapseObjectLiteral1() { // Verify collapseObjectLiterals does nothing in global scope String code = "var x = {}; x.FOO = 5; x.bar = 3;"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.collapseObjectLiterals = true; testSame(options, code); } public void testCollapseObjectLiteral2() { String code = "function f() {var x = {}; x.FOO = 5; x.bar = 3;}"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.collapseObjectLiterals = true; test(options, code, "function f(){" + "var JSCompiler_object_inline_FOO_0;" + "var JSCompiler_object_inline_bar_1;" + "JSCompiler_object_inline_FOO_0=5;" + "JSCompiler_object_inline_bar_1=3}"); } public void testTightenTypesWithoutTypeCheck() { CompilerOptions options = createCompilerOptions(); options.tightenTypes = true; test(options, "", DefaultPassConfig.TIGHTEN_TYPES_WITHOUT_TYPE_CHECK); } public void testDisambiguateProperties() { String code = "/** @constructor */ function Foo(){} Foo.prototype.bar = 3;" + "/** @constructor */ function Baz(){} Baz.prototype.bar = 3;"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.disambiguateProperties = true; options.checkTypes = true; test(options, code, "function Foo(){} Foo.prototype.Foo_prototype$bar = 3;" + "function Baz(){} Baz.prototype.Baz_prototype$bar = 3;"); } public void testMarkPureCalls() { String testCode = "function foo() {} foo();"; CompilerOptions options = createCompilerOptions(); options.removeDeadCode = true; testSame(options, testCode); options.computeFunctionSideEffects = true; test(options, testCode, "function foo() {}"); } public void testMarkNoSideEffects() { String testCode = "noSideEffects();"; CompilerOptions options = createCompilerOptions(); options.removeDeadCode = true; testSame(options, testCode); options.markNoSideEffectCalls = true; test(options, testCode, ""); } public void testChainedCalls() { CompilerOptions options = createCompilerOptions(); options.chainCalls = true; test( options, "/** @constructor */ function Foo() {} " + "Foo.prototype.bar = function() { return this; }; " + "var f = new Foo();" + "f.bar(); " + "f.bar(); ", "function Foo() {} " + "Foo.prototype.bar = function() { return this; }; " + "var f = new Foo();" + "f.bar().bar();"); } public void testExtraAnnotationNames() { CompilerOptions options = createCompilerOptions(); options.setExtraAnnotationNames(Sets.newHashSet("TagA", "TagB")); test( options, "/** @TagA */ var f = new Foo(); /** @TagB */ f.bar();", "var f = new Foo(); f.bar();"); } public void testDevirtualizePrototypeMethods() { CompilerOptions options = createCompilerOptions(); options.devirtualizePrototypeMethods = true; test( options, "/** @constructor */ var Foo = function() {}; " + "Foo.prototype.bar = function() {};" + "(new Foo()).bar();", "var Foo = function() {};" + "var JSCompiler_StaticMethods_bar = " + " function(JSCompiler_StaticMethods_bar$self) {};" + "JSCompiler_StaticMethods_bar(new Foo());"); } public void testCheckConsts() { CompilerOptions options = createCompilerOptions(); options.inlineConstantVars = true; test(options, "var FOO = true; FOO = false", ConstCheck.CONST_REASSIGNED_VALUE_ERROR); } public void testAllChecksOn() { CompilerOptions options = createCompilerOptions(); options.checkSuspiciousCode = true; options.checkControlStructures = true; options.checkRequires = CheckLevel.ERROR; options.checkProvides = CheckLevel.ERROR; options.generateExports = true; options.exportTestFunctions = true; options.closurePass = true; options.checkMissingGetCssNameLevel = CheckLevel.ERROR; options.checkMissingGetCssNameBlacklist = "goog"; options.syntheticBlockStartMarker = "synStart"; options.syntheticBlockEndMarker = "synEnd"; options.checkSymbols = true; options.aggressiveVarCheck = CheckLevel.ERROR; options.processObjectPropertyString = true; options.collapseProperties = true; test(options, CLOSURE_BOILERPLATE, CLOSURE_COMPILED); } public void testTypeCheckingWithSyntheticBlocks() { CompilerOptions options = createCompilerOptions(); options.syntheticBlockStartMarker = "synStart"; options.syntheticBlockEndMarker = "synEnd"; options.checkTypes = true; // We used to have a bug where the CFG drew an // edge straight from synStart to f(progress). // If that happens, then progress will get type {number|undefined}. testSame( options, "/** @param {number} x */ function f(x) {}" + "function g() {" + " synStart('foo');" + " var progress = 1;" + " f(progress);" + " synEnd('foo');" + "}"); } public void testCompilerDoesNotBlowUpIfUndefinedSymbols() { CompilerOptions options = createCompilerOptions(); options.checkSymbols = true; // Disable the undefined variable check. options.setWarningLevel( DiagnosticGroup.forType(VarCheck.UNDEFINED_VAR_ERROR), CheckLevel.OFF); // The compiler used to throw an IllegalStateException on this. testSame(options, "var x = {foo: y};"); } // Make sure that if we change variables which are constant to have // $$constant appended to their names, we remove that tag before // we finish. public void testConstantTagsMustAlwaysBeRemoved() { CompilerOptions options = createCompilerOptions(); options.variableRenaming = VariableRenamingPolicy.LOCAL; String originalText = "var G_GEO_UNKNOWN_ADDRESS=1;\n" + "function foo() {" + " var localVar = 2;\n" + " if (G_GEO_UNKNOWN_ADDRESS == localVar) {\n" + " alert(\"A\"); }}"; String expectedText = "var G_GEO_UNKNOWN_ADDRESS=1;" + "function foo(){var a=2;if(G_GEO_UNKNOWN_ADDRESS==a){alert(\"A\")}}"; test(options, originalText, expectedText); } public void testClosurePassPreservesJsDoc() { CompilerOptions options = createCompilerOptions(); options.checkTypes = true; options.closurePass = true; test(options, CLOSURE_BOILERPLATE + "goog.provide('Foo'); /** @constructor */ Foo = function() {};" + "var x = new Foo();", "var COMPILED=true;var goog={};goog.exportSymbol=function(){};" + "var Foo=function(){};var x=new Foo"); test(options, CLOSURE_BOILERPLATE + "goog.provide('Foo'); /** @enum */ Foo = {a: 3};", TypeCheck.ENUM_NOT_CONSTANT); } public void testProvidedNamespaceIsConst() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.inlineConstantVars = true; options.collapseProperties = true; test(options, "var goog = {}; goog.provide('foo'); " + "function f() { foo = {};}", "var foo = {}; function f() { foo = {}; }", ConstCheck.CONST_REASSIGNED_VALUE_ERROR); } public void testProvidedNamespaceIsConst2() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.inlineConstantVars = true; options.collapseProperties = true; test(options, "var goog = {}; goog.provide('foo.bar'); " + "function f() { foo.bar = {};}", "var foo$bar = {};" + "function f() { foo$bar = {}; }", ConstCheck.CONST_REASSIGNED_VALUE_ERROR); } public void testProvidedNamespaceIsConst3() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.inlineConstantVars = true; options.collapseProperties = true; test(options, "var goog = {}; " + "goog.provide('foo.bar'); goog.provide('foo.bar.baz'); " + "/** @constructor */ foo.bar = function() {};" + "/** @constructor */ foo.bar.baz = function() {};", "var foo$bar = function(){};" + "var foo$bar$baz = function(){};"); } public void testProvidedNamespaceIsConst4() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.inlineConstantVars = true; options.collapseProperties = true; test(options, "var goog = {}; goog.provide('foo.Bar'); " + "var foo = {}; foo.Bar = {};", "var foo = {}; foo = {}; foo.Bar = {};"); } public void testProvidedNamespaceIsConst5() { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.inlineConstantVars = true; options.collapseProperties = true; test(options, "var goog = {}; goog.provide('foo.Bar'); " + "foo = {}; foo.Bar = {};", "var foo = {}; foo = {}; foo.Bar = {};"); } public void testProcessDefinesAlwaysOn() { test(createCompilerOptions(), "/** @define {boolean} */ var HI = true; HI = false;", "var HI = false;false;"); } public void testProcessDefinesAdditionalReplacements() { CompilerOptions options = createCompilerOptions(); options.setDefineToBooleanLiteral("HI", false); test(options, "/** @define {boolean} */ var HI = true;", "var HI = false;"); } public void testReplaceMessages() { CompilerOptions options = createCompilerOptions(); String prefix = "var goog = {}; goog.getMsg = function() {};"; testSame(options, prefix + "var MSG_HI = goog.getMsg('hi');"); options.messageBundle = new EmptyMessageBundle(); test(options, prefix + "/** @desc xyz */ var MSG_HI = goog.getMsg('hi');", prefix + "var MSG_HI = 'hi';"); } public void testCheckGlobalNames() { CompilerOptions options = createCompilerOptions(); options.checkGlobalNamesLevel = CheckLevel.ERROR; test(options, "var x = {}; var y = x.z;", CheckGlobalNames.UNDEFINED_NAME_WARNING); } public void testInlineGetters() { CompilerOptions options = createCompilerOptions(); String code = "function Foo() {} Foo.prototype.bar = function() { return 3; };" + "var x = new Foo(); x.bar();"; testSame(options, code); options.inlineGetters = true; test(options, code, "function Foo() {} Foo.prototype.bar = function() { return 3 };" + "var x = new Foo(); 3;"); } public void testInlineGettersWithAmbiguate() { CompilerOptions options = createCompilerOptions(); String code = "/** @constructor */" + "function Foo() {}" + "/** @type {number} */ Foo.prototype.field;" + "Foo.prototype.getField = function() { return this.field; };" + "/** @constructor */" + "function Bar() {}" + "/** @type {string} */ Bar.prototype.field;" + "Bar.prototype.getField = function() { return this.field; };" + "new Foo().getField();" + "new Bar().getField();"; testSame(options, code); options.inlineGetters = true; test(options, code, "function Foo() {}" + "Foo.prototype.field;" + "Foo.prototype.getField = function() { return this.field; };" + "function Bar() {}" + "Bar.prototype.field;" + "Bar.prototype.getField = function() { return this.field; };" + "new Foo().field;" + "new Bar().field;"); options.checkTypes = true; options.ambiguateProperties = true; // Propagating the wrong type information may cause ambiguate properties // to generate bad code. testSame(options, code); } public void testInlineVariables() { CompilerOptions options = createCompilerOptions(); String code = "function foo() {} var x = 3; foo(x);"; testSame(options, code); options.inlineVariables = true; test(options, code, "(function foo() {})(3);"); options.propertyRenaming = PropertyRenamingPolicy.HEURISTIC; test(options, code, DefaultPassConfig.CANNOT_USE_PROTOTYPE_AND_VAR); } public void testInlineConstants() { CompilerOptions options = createCompilerOptions(); String code = "function foo() {} var x = 3; foo(x); var YYY = 4; foo(YYY);"; testSame(options, code); options.inlineConstantVars = true; test(options, code, "function foo() {} var x = 3; foo(x); foo(4);"); } public void testMinimizeExits() { CompilerOptions options = createCompilerOptions(); String code = "function f() {" + " if (window.foo) return; window.h(); " + "}"; testSame(options, code); options.foldConstants = true; test( options, code, "function f() {" + " window.foo || window.h(); " + "}"); } public void testFoldConstants() { CompilerOptions options = createCompilerOptions(); String code = "if (true) { window.foo(); }"; testSame(options, code); options.foldConstants = true; test(options, code, "window.foo();"); } public void testRemoveUnreachableCode() { CompilerOptions options = createCompilerOptions(); String code = "function f() { return; f(); }"; testSame(options, code); options.removeDeadCode = true; test(options, code, "function f() {}"); } public void testRemoveUnusedPrototypeProperties1() { CompilerOptions options = createCompilerOptions(); String code = "function Foo() {} " + "Foo.prototype.bar = function() { return new Foo(); };"; testSame(options, code); options.removeUnusedPrototypeProperties = true; test(options, code, "function Foo() {}"); } public void testRemoveUnusedPrototypeProperties2() { CompilerOptions options = createCompilerOptions(); String code = "function Foo() {} " + "Foo.prototype.bar = function() { return new Foo(); };" + "function f(x) { x.bar(); }"; testSame(options, code); options.removeUnusedPrototypeProperties = true; testSame(options, code); options.removeUnusedVars = true; test(options, code, ""); } public void testSmartNamePass() { CompilerOptions options = createCompilerOptions(); String code = "function Foo() { this.bar(); } " + "Foo.prototype.bar = function() { return Foo(); };"; testSame(options, code); options.smartNameRemoval = true; test(options, code, ""); } public void testDeadAssignmentsElimination() { CompilerOptions options = createCompilerOptions(); String code = "function f() { var x = 3; 4; x = 5; return x; } f(); "; testSame(options, code); options.deadAssignmentElimination = true; testSame(options, code); options.removeUnusedVars = true; test(options, code, "function f() { var x = 3; 4; x = 5; return x; } f();"); } public void testInlineFunctions() { CompilerOptions options = createCompilerOptions(); String code = "function f() { return 3; } f(); "; testSame(options, code); options.inlineFunctions = true; test(options, code, "3;"); } public void testRemoveUnusedVars1() { CompilerOptions options = createCompilerOptions(); String code = "function f(x) {} f();"; testSame(options, code); options.removeUnusedVars = true; test(options, code, "function f() {} f();"); } public void testRemoveUnusedVars2() { CompilerOptions options = createCompilerOptions(); String code = "(function f(x) {})();var g = function() {}; g();"; testSame(options, code); options.removeUnusedVars = true; test(options, code, "(function() {})();var g = function() {}; g();"); options.anonymousFunctionNaming = AnonymousFunctionNamingPolicy.UNMAPPED; test(options, code, "(function f() {})();var g = function $g$() {}; g();"); } public void testCrossModuleCodeMotion() { CompilerOptions options = createCompilerOptions(); String[] code = new String[] { "var x = 1;", "x;", }; testSame(options, code); options.crossModuleCodeMotion = true; test(options, code, new String[] { "", "var x = 1; x;", }); } public void testCrossModuleMethodMotion() { CompilerOptions options = createCompilerOptions(); String[] code = new String[] { "var Foo = function() {}; Foo.prototype.bar = function() {};" + "var x = new Foo();", "x.bar();", }; testSame(options, code); options.crossModuleMethodMotion = true; test(options, code, new String[] { CrossModuleMethodMotion.STUB_DECLARATIONS + "var Foo = function() {};" + "Foo.prototype.bar=JSCompiler_stubMethod(0); var x=new Foo;", "Foo.prototype.bar=JSCompiler_unstubMethod(0,function(){}); x.bar()", }); } public void testFlowSensitiveInlineVariables1() { CompilerOptions options = createCompilerOptions(); String code = "function f() { var x = 3; x = 5; return x; }"; testSame(options, code); options.flowSensitiveInlineVariables = true; test(options, code, "function f() { var x = 3; return 5; }"); String unusedVar = "function f() { var x; x = 5; return x; } f()"; test(options, unusedVar, "function f() { var x; return 5; } f()"); options.removeUnusedVars = true; test(options, unusedVar, "function f() { return 5; } f()"); } public void testFlowSensitiveInlineVariables2() { CompilerOptions options = createCompilerOptions(); CompilationLevel.SIMPLE_OPTIMIZATIONS .setOptionsForCompilationLevel(options); test(options, "function f () {\n" + " var ab = 0;\n" + " ab += '-';\n" + " alert(ab);\n" + "}", "function f () {\n" + " alert('0-');\n" + "}"); } public void testCollapseAnonymousFunctions() { CompilerOptions options = createCompilerOptions(); String code = "var f = function() {};"; testSame(options, code); options.collapseAnonymousFunctions = true; test(options, code, "function f() {}"); } public void testMoveFunctionDeclarations() { CompilerOptions options = createCompilerOptions(); String code = "var x = f(); function f() { return 3; }"; testSame(options, code); options.moveFunctionDeclarations = true; test(options, code, "function f() { return 3; } var x = f();"); } public void testNameAnonymousFunctions() { CompilerOptions options = createCompilerOptions(); String code = "var f = function() {};"; testSame(options, code); options.anonymousFunctionNaming = AnonymousFunctionNamingPolicy.MAPPED; test(options, code, "var f = function $() {}"); assertNotNull(lastCompiler.getResult().namedAnonFunctionMap); options.anonymousFunctionNaming = AnonymousFunctionNamingPolicy.UNMAPPED; test(options, code, "var f = function $f$() {}"); assertNull(lastCompiler.getResult().namedAnonFunctionMap); } public void testNameAnonymousFunctionsWithVarRemoval() { CompilerOptions options = createCompilerOptions(); options.setRemoveUnusedVariables(CompilerOptions.Reach.LOCAL_ONLY); options.setInlineVariables(true); String code = "var f = function longName() {}; var g = function() {};" + "function longerName() {} var i = longerName;"; test(options, code, "var f = function() {}; var g = function() {}; " + "var i = function() {};"); options.anonymousFunctionNaming = AnonymousFunctionNamingPolicy.MAPPED; test(options, code, "var f = function longName() {}; var g = function $() {};" + "var i = function longerName(){};"); assertNotNull(lastCompiler.getResult().namedAnonFunctionMap); options.anonymousFunctionNaming = AnonymousFunctionNamingPolicy.UNMAPPED; test(options, code, "var f = function longName() {}; var g = function $g$() {};" + "var i = function longerName(){};"); assertNull(lastCompiler.getResult().namedAnonFunctionMap); } public void testExtractPrototypeMemberDeclarations() { CompilerOptions options = createCompilerOptions(); String code = "var f = function() {};"; String expected = "var a; var b = function() {}; a = b.prototype;"; for (int i = 0; i < 10; i++) { code += "f.prototype.a = " + i + ";"; expected += "a.a = " + i + ";"; } testSame(options, code); options.extractPrototypeMemberDeclarations = true; options.variableRenaming = VariableRenamingPolicy.ALL; test(options, code, expected); options.propertyRenaming = PropertyRenamingPolicy.HEURISTIC; options.variableRenaming = VariableRenamingPolicy.OFF; testSame(options, code); } public void testDevirtualizationAndExtractPrototypeMemberDeclarations() { CompilerOptions options = createCompilerOptions(); options.devirtualizePrototypeMethods = true; options.collapseAnonymousFunctions = true; options.extractPrototypeMemberDeclarations = true; options.variableRenaming = VariableRenamingPolicy.ALL; String code = "var f = function() {};"; String expected = "var a; function b() {} a = b.prototype;"; for (int i = 0; i < 10; i++) { code += "f.prototype.argz = function() {arguments};"; code += "f.prototype.devir" + i + " = function() {};"; char letter = (char) ('d' + i); expected += "a.argz = function() {arguments};"; expected += "function " + letter + "(c){}"; } code += "var F = new f(); F.argz();"; expected += "var n = new b(); n.argz();"; for (int i = 0; i < 10; i++) { code += "F.devir" + i + "();"; char letter = (char) ('d' + i); expected += letter + "(n);"; } test(options, code, expected); } public void testCoalesceVariableNames() { CompilerOptions options = createCompilerOptions(); String code = "function f() {var x = 3; var y = x; var z = y; return z;}"; testSame(options, code); options.coalesceVariableNames = true; test(options, code, "function f() {var x = 3; x = x; x = x; return x;}"); } public void testPropertyRenaming() { CompilerOptions options = createCompilerOptions(); options.propertyAffinity = true; String code = "function f() { return this.foo + this['bar'] + this.Baz; }" + "f.prototype.bar = 3; f.prototype.Baz = 3;"; String heuristic = "function f() { return this.foo + this['bar'] + this.a; }" + "f.prototype.bar = 3; f.prototype.a = 3;"; String aggHeuristic = "function f() { return this.foo + this['b'] + this.a; } " + "f.prototype.b = 3; f.prototype.a = 3;"; String all = "function f() { return this.b + this['bar'] + this.a; }" + "f.prototype.c = 3; f.prototype.a = 3;"; testSame(options, code); options.propertyRenaming = PropertyRenamingPolicy.HEURISTIC; test(options, code, heuristic); options.propertyRenaming = PropertyRenamingPolicy.AGGRESSIVE_HEURISTIC; test(options, code, aggHeuristic); options.propertyRenaming = PropertyRenamingPolicy.ALL_UNQUOTED; test(options, code, all); } public void testConvertToDottedProperties() { CompilerOptions options = createCompilerOptions(); String code = "function f() { return this['bar']; } f.prototype.bar = 3;"; String expected = "function f() { return this.bar; } f.prototype.a = 3;"; testSame(options, code); options.convertToDottedProperties = true; options.propertyRenaming = PropertyRenamingPolicy.ALL_UNQUOTED; test(options, code, expected); } public void testRewriteFunctionExpressions() { CompilerOptions options = createCompilerOptions(); String code = "var a = function() {};"; String expected = "function JSCompiler_emptyFn(){return function(){}} " + "var a = JSCompiler_emptyFn();"; for (int i = 0; i < 10; i++) { code += "a = function() {};"; expected += "a = JSCompiler_emptyFn();"; } testSame(options, code); options.rewriteFunctionExpressions = true; test(options, code, expected); } public void testAliasAllStrings() { CompilerOptions options = createCompilerOptions(); String code = "function f() { return 'a'; }"; String expected = "var $$S_a = 'a'; function f() { return $$S_a; }"; testSame(options, code); options.aliasAllStrings = true; test(options, code, expected); } public void testAliasExterns() { CompilerOptions options = createCompilerOptions(); String code = "function f() { return window + window + window + window; }"; String expected = "var GLOBAL_window = window;" + "function f() { return GLOBAL_window + GLOBAL_window + " + " GLOBAL_window + GLOBAL_window; }"; testSame(options, code); options.aliasExternals = true; test(options, code, expected); } public void testAliasKeywords() { CompilerOptions options = createCompilerOptions(); String code = "function f() { return true + true + true + true + true + true; }"; String expected = "var JSCompiler_alias_TRUE = true;" + "function f() { return JSCompiler_alias_TRUE + " + " JSCompiler_alias_TRUE + JSCompiler_alias_TRUE + " + " JSCompiler_alias_TRUE + JSCompiler_alias_TRUE + " + " JSCompiler_alias_TRUE; }"; testSame(options, code); options.aliasKeywords = true; test(options, code, expected); } public void testRenameVars1() { CompilerOptions options = createCompilerOptions(); String code = "var abc = 3; function f() { var xyz = 5; return abc + xyz; }"; String local = "var abc = 3; function f() { var a = 5; return abc + a; }"; String all = "var a = 3; function c() { var b = 5; return a + b; }"; testSame(options, code); options.variableRenaming = VariableRenamingPolicy.LOCAL; test(options, code, local); options.variableRenaming = VariableRenamingPolicy.ALL; test(options, code, all); options.reserveRawExports = true; } public void testRenameVars2() { CompilerOptions options = createCompilerOptions(); options.variableRenaming = VariableRenamingPolicy.ALL; String code = "var abc = 3; function f() { window['a'] = 5; }"; String noexport = "var a = 3; function b() { window['a'] = 5; }"; String export = "var b = 3; function c() { window['a'] = 5; }"; options.reserveRawExports = false; test(options, code, noexport); options.reserveRawExports = true; test(options, code, export); } public void testShadowVaribles() { CompilerOptions options = createCompilerOptions(); options.variableRenaming = VariableRenamingPolicy.LOCAL; options.shadowVariables = true; String code = "var f = function(x) { return function(y) {}}"; String expected = "var f = function(a) { return function(a) {}}"; test(options, code, expected); } public void testRenameLabels() { CompilerOptions options = createCompilerOptions(); String code = "longLabel: for(;true;) { break longLabel; }"; String expected = "a: for(;true;) { break a; }"; testSame(options, code); options.labelRenaming = true; test(options, code, expected); } public void testBadBreakStatementInIdeMode() { // Ensure that type-checking doesn't crash, even if the CFG is malformed. // This can happen in IDE mode. CompilerOptions options = createCompilerOptions(); options.ideMode = true; options.checkTypes = true; test(options, "function f() { try { } catch(e) { break; } }", RhinoErrorReporter.PARSE_ERROR); } public void testIssue63SourceMap() { CompilerOptions options = createCompilerOptions(); String code = "var a;"; options.skipAllPasses = true; options.sourceMapOutputPath = "./src.map"; Compiler compiler = compile(options, code); compiler.toSource(); } public void testRegExp1() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; String code = "/(a)/.test(\"a\");"; testSame(options, code); options.computeFunctionSideEffects = true; String expected = ""; test(options, code, expected); } public void testRegExp2() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; String code = "/(a)/.test(\"a\");var a = RegExp.$1"; testSame(options, code); options.computeFunctionSideEffects = true; test(options, code, CheckRegExp.REGEXP_REFERENCE); options.setWarningLevel(DiagnosticGroups.CHECK_REGEXP, CheckLevel.OFF); testSame(options, code); } public void testFoldLocals1() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; // An external object, whose constructor has no side-effects, // and whose method "go" only modifies the object. String code = "new Widget().go();"; testSame(options, code); options.computeFunctionSideEffects = true; test(options, code, ""); } public void testFoldLocals2() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; options.checkTypes = true; // An external function that returns a local object that the // method "go" that only modifies the object. String code = "widgetToken().go();"; testSame(options, code); options.computeFunctionSideEffects = true; test(options, code, "widgetToken()"); } public void testFoldLocals3() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; // A function "f" who returns a known local object, and a method that // modifies only modifies that. String definition = "function f(){return new Widget()}"; String call = "f().go();"; String code = definition + call; testSame(options, code); options.computeFunctionSideEffects = true; // BROKEN //test(options, code, definition); testSame(options, code); } public void testFoldLocals4() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; String code = "/** @constructor */\n" + "function InternalWidget(){this.x = 1;}" + "InternalWidget.prototype.internalGo = function (){this.x = 2};" + "new InternalWidget().internalGo();"; testSame(options, code); options.computeFunctionSideEffects = true; String optimized = "" + "function InternalWidget(){this.x = 1;}" + "InternalWidget.prototype.internalGo = function (){this.x = 2};"; test(options, code, optimized); } public void testFoldLocals5() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; String code = "" + "function fn(){var a={};a.x={};return a}" + "fn().x.y = 1;"; // "fn" returns a unescaped local object, we should be able to fold it, // but we don't currently. String result = "" + "function fn(){var a={x:{}};return a}" + "fn().x.y = 1;"; test(options, code, result); options.computeFunctionSideEffects = true; test(options, code, result); } public void testFoldLocals6() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; String code = "" + "function fn(){return {}}" + "fn().x.y = 1;"; testSame(options, code); options.computeFunctionSideEffects = true; testSame(options, code); } public void testFoldLocals7() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; String code = "" + "function InternalWidget(){return [];}" + "Array.prototype.internalGo = function (){this.x = 2};" + "InternalWidget().internalGo();"; testSame(options, code); options.computeFunctionSideEffects = true; String optimized = "" + "function InternalWidget(){return [];}" + "Array.prototype.internalGo = function (){this.x = 2};"; test(options, code, optimized); } public void testVarDeclarationsIntoFor() { CompilerOptions options = createCompilerOptions(); options.collapseVariableDeclarations = false; String code = "var a = 1; for (var b = 2; ;) {}"; testSame(options, code); options.collapseVariableDeclarations = true; test(options, code, "for (var a = 1, b = 2; ;) {}"); } public void testExploitAssigns() { CompilerOptions options = createCompilerOptions(); options.collapseVariableDeclarations = false; String code = "a = 1; b = a; c = b"; testSame(options, code); options.collapseVariableDeclarations = true; test(options, code, "c=b=a=1"); } public void testRecoverOnBadExterns() throws Exception { // This test is for a bug in a very narrow set of circumstances: // 1) externs validation has to be off. // 2) aliasExternals has to be on. // 3) The user has to reference a "normal" variable in externs. // This case is handled at checking time by injecting a // synthetic extern variable, and adding a "@suppress {duplicate}" to // the normal code at compile time. But optimizations may remove that // annotation, so we need to make sure that the variable declarations // are de-duped before that happens. CompilerOptions options = createCompilerOptions(); options.aliasExternals = true; externs = ImmutableList.of( SourceFile.fromCode("externs", "extern.foo")); test(options, "var extern; " + "function f() { return extern + extern + extern + extern; }", "var extern; " + "function f() { return extern + extern + extern + extern; }", VarCheck.UNDEFINED_EXTERN_VAR_ERROR); } public void testDuplicateVariablesInExterns() { CompilerOptions options = createCompilerOptions(); options.checkSymbols = true; externs = ImmutableList.of( SourceFile.fromCode("externs", "var externs = {}; /** @suppress {duplicate} */ var externs = {};")); testSame(options, ""); } public void testLanguageMode() { CompilerOptions options = createCompilerOptions(); options.setLanguageIn(LanguageMode.ECMASCRIPT3); String code = "var a = {get f(){}}"; Compiler compiler = compile(options, code); checkUnexpectedErrorsOrWarnings(compiler, 1); assertEquals( "JSC_PARSE_ERROR. Parse error. " + "getters are not supported in older versions of JS. " + "If you are targeting newer versions of JS, " + "set the appropriate language_in option. " + "at i0 line 1 : 0", compiler.getErrors()[0].toString()); options.setLanguageIn(LanguageMode.ECMASCRIPT5); testSame(options, code); options.setLanguageIn(LanguageMode.ECMASCRIPT5_STRICT); testSame(options, code); } public void testLanguageMode2() { CompilerOptions options = createCompilerOptions(); options.setLanguageIn(LanguageMode.ECMASCRIPT3); options.setWarningLevel(DiagnosticGroups.ES5_STRICT, CheckLevel.OFF); String code = "var a = 2; delete a;"; testSame(options, code); options.setLanguageIn(LanguageMode.ECMASCRIPT5); testSame(options, code); options.setLanguageIn(LanguageMode.ECMASCRIPT5_STRICT); test(options, code, code, StrictModeCheck.DELETE_VARIABLE); } public void testIssue598() { CompilerOptions options = createCompilerOptions(); options.setLanguageIn(LanguageMode.ECMASCRIPT5_STRICT); WarningLevel.VERBOSE.setOptionsForWarningLevel(options); options.setLanguageIn(LanguageMode.ECMASCRIPT5); String code = "'use strict';\n" + "function App() {}\n" + "App.prototype = {\n" + " get appData() { return this.appData_; },\n" + " set appData(data) { this.appData_ = data; }\n" + "};"; Compiler compiler = compile(options, code); testSame(options, code); } public void testIssue701() { // Check ASCII art in license comments. String ascii = "/**\n" + " * @preserve\n" + " This\n" + " is\n" + " ASCII ART\n" + "*/"; String result = "/*\n\n" + " This\n" + " is\n" + " ASCII ART\n" + "*/\n"; testSame(createCompilerOptions(), ascii); assertEquals(result, lastCompiler.toSource()); } public void testIssue724() { CompilerOptions options = createCompilerOptions(); CompilationLevel.ADVANCED_OPTIMIZATIONS .setOptionsForCompilationLevel(options); String code = "isFunction = function(functionToCheck) {" + " var getType = {};" + " return functionToCheck && " + " getType.toString.apply(functionToCheck) === " + " '[object Function]';" + "};"; String result = "isFunction=function(a){var b={};" + "return a&&\"[object Function]\"===b.b.a(a)}"; test(options, code, result); } public void testIssue730() { CompilerOptions options = createCompilerOptions(); CompilationLevel.ADVANCED_OPTIMIZATIONS .setOptionsForCompilationLevel(options); String code = "/** @constructor */function A() {this.foo = 0; Object.seal(this);}\n" + "/** @constructor */function B() {this.a = new A();}\n" + "B.prototype.dostuff = function() {this.a.foo++;alert('hi');}\n" + "new B().dostuff();\n"; test(options, code, "function a(){this.b=0;Object.seal(this)}" + "(new function(){this.a=new a}).a.b++;" + "alert(\"hi\")"); options.removeUnusedClassProperties = true; // This is still a problem when removeUnusedClassProperties are enabled. test(options, code, "function a(){Object.seal(this)}" + "(new function(){this.a=new a}).a.b++;" + "alert(\"hi\")"); } public void testCoaleseVariables() { CompilerOptions options = createCompilerOptions(); options.foldConstants = false; options.coalesceVariableNames = true; String code = "function f(a) {" + " if (a) {" + " return a;" + " } else {" + " var b = a;" + " return b;" + " }" + " return a;" + "}"; String expected = "function f(a) {" + " if (a) {" + " return a;" + " } else {" + " a = a;" + " return a;" + " }" + " return a;" + "}"; test(options, code, expected); options.foldConstants = true; options.coalesceVariableNames = false; code = "function f(a) {" + " if (a) {" + " return a;" + " } else {" + " var b = a;" + " return b;" + " }" + " return a;" + "}"; expected = "function f(a) {" + " if (!a) {" + " var b = a;" + " return b;" + " }" + " return a;" + "}"; test(options, code, expected); options.foldConstants = true; options.coalesceVariableNames = true; expected = "function f(a) {" + " return a;" + "}"; test(options, code, expected); } public void testLateStatementFusion() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; test(options, "while(a){a();if(b){b();b()}}", "for(;a;)a(),b&&(b(),b())"); } public void testLateConstantReordering() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; test(options, "if (x < 1 || x > 1 || 1 < x || 1 > x) { alert(x) }", " (1 > x || 1 < x || 1 < x || 1 > x) && alert(x) "); } public void testsyntheticBlockOnDeadAssignments() { CompilerOptions options = createCompilerOptions(); options.deadAssignmentElimination = true; options.removeUnusedVars = true; options.syntheticBlockStartMarker = "START"; options.syntheticBlockEndMarker = "END"; test(options, "var x; x = 1; START(); x = 1;END();x()", "var x; x = 1;{START();{x = 1}END()}x()"); } public void testBug4152835() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; options.syntheticBlockStartMarker = "START"; options.syntheticBlockEndMarker = "END"; test(options, "START();END()", "{START();{}END()}"); } public void testBug5786871() { CompilerOptions options = createCompilerOptions(); options.ideMode = true; test(options, "function () {}", RhinoErrorReporter.PARSE_ERROR); } public void testIssue378() { CompilerOptions options = createCompilerOptions(); options.inlineVariables = true; options.flowSensitiveInlineVariables = true; testSame(options, "function f(c) {var f = c; arguments[0] = this;" + " f.apply(this, arguments); return this;}"); } public void testIssue550() { CompilerOptions options = createCompilerOptions(); CompilationLevel.SIMPLE_OPTIMIZATIONS .setOptionsForCompilationLevel(options); options.foldConstants = true; options.inlineVariables = true; options.flowSensitiveInlineVariables = true; test(options, "function f(h) {\n" + " var a = h;\n" + " a = a + 'x';\n" + " a = a + 'y';\n" + " return a;\n" + "}", // This should eventually get inlined completely. "function f(a) { a += 'x'; return a += 'y'; }"); } public void testIssue284() { CompilerOptions options = createCompilerOptions(); options.smartNameRemoval = true; test(options, "var goog = {};" + "goog.inherits = function(x, y) {};" + "var ns = {};" + "/** @constructor */" + "ns.PageSelectionModel = function() {};" + "/** @constructor */" + "ns.PageSelectionModel.FooEvent = function() {};" + "/** @constructor */" + "ns.PageSelectionModel.SelectEvent = function() {};" + "goog.inherits(ns.PageSelectionModel.ChangeEvent," + " ns.PageSelectionModel.FooEvent);", ""); } public void testIssue772() throws Exception { CompilerOptions options = createCompilerOptions(); options.closurePass = true; options.checkTypes = true; test( options, "/** @const */ var a = {};" + "/** @const */ a.b = {};" + "/** @const */ a.b.c = {};" + "goog.scope(function() {" + " var b = a.b;" + " var c = b.c;" + " /** @typedef {string} */" + " c.MyType;" + " /** @param {c.MyType} x The variable. */" + " c.myFunc = function(x) {};" + "});", "/** @const */ var a = {};" + "/** @const */ a.b = {};" + "/** @const */ a.b.c = {};" + "a.b.c.MyType;" + "a.b.c.myFunc = function(x) {};"); } public void testCodingConvention() { Compiler compiler = new Compiler(); compiler.initOptions(new CompilerOptions()); assertEquals( compiler.getCodingConvention().getClass().toString(), ClosureCodingConvention.class.toString()); } public void testJQueryStringSplitLoops() { CompilerOptions options = createCompilerOptions(); options.foldConstants = true; test(options, "var x=['1','2','3','4','5','6','7']", "var x='1234567'.split('')"); options = createCompilerOptions(); options.foldConstants = true; options.computeFunctionSideEffects = false; options.removeUnusedVars = true; // If we do splits too early, it would add a side-effect to x. test(options, "var x=['1','2','3','4','5','6','7']", ""); } public void testAlwaysRunSafetyCheck() { CompilerOptions options = createCompilerOptions(); options.checkSymbols = false; options.customPasses = ArrayListMultimap.create(); options.customPasses.put( CustomPassExecutionTime.BEFORE_OPTIMIZATIONS, new CompilerPass() { @Override public void process(Node externs, Node root) { Node var = root.getLastChild().getFirstChild(); assertEquals(Token.VAR, var.getType()); var.detachFromParent(); } }); try { test(options, "var x = 3; function f() { return x + z; }", "function f() { return x + z; }"); fail("Expected run-time exception"); } catch (RuntimeException e) { assertTrue(e.getMessage().indexOf("Unexpected variable x") != -1); } } public void testSuppressEs5StrictWarning() { CompilerOptions options = createCompilerOptions(); options.setWarningLevel(DiagnosticGroups.ES5_STRICT, CheckLevel.WARNING); test(options, "/** @suppress{es5Strict} */\n" + "function f() { var arguments; }", "function f() {}"); } public void testCheckProvidesWarning() { CompilerOptions options = createCompilerOptions(); options.setWarningLevel(DiagnosticGroups.CHECK_PROVIDES, CheckLevel.WARNING); options.setCheckProvides(CheckLevel.WARNING); test(options, "/** @constructor */\n" + "function f() { var arguments; }", DiagnosticType.warning("JSC_MISSING_PROVIDE", "missing goog.provide(''{0}'')")); } public void testSuppressCheckProvidesWarning() { CompilerOptions options = createCompilerOptions(); options.setWarningLevel(DiagnosticGroups.CHECK_PROVIDES, CheckLevel.WARNING); options.setCheckProvides(CheckLevel.WARNING); testSame(options, "/** @constructor\n" + " * @suppress{checkProvides} */\n" + "function f() {}"); } public void testSuppressCastWarning() { CompilerOptions options = createCompilerOptions(); options.setWarningLevel(DiagnosticGroups.CHECK_TYPES, CheckLevel.WARNING); normalizeResults = true; test(options, "function f() { var xyz = /** @type {string} */ (0); }", DiagnosticType.warning( "JSC_INVALID_CAST", "invalid cast")); testSame(options, "/** @suppress{cast} */\n" + "function f() { var xyz = /** @type {string} */ (0); }"); } public void testRenamePrefix() { String code = "var x = {}; function f(y) {}"; CompilerOptions options = createCompilerOptions(); options.renamePrefix = "G_"; options.variableRenaming = VariableRenamingPolicy.ALL; test(options, code, "var G_={}; function G_a(a) {}"); } public void testRenamePrefixNamespace() { String code = "var x = {}; x.FOO = 5; x.bar = 3;"; CompilerOptions options = createCompilerOptions(); testSame(options, code); options.collapseProperties = true; options.renamePrefixNamespace = "_"; test(options, code, "_.x$FOO = 5; _.x$bar = 3;"); } public void testRenamePrefixNamespaceProtectSideEffects() { String code = "var x = null; try { +x.FOO; } catch (e) {}"; CompilerOptions options = createCompilerOptions(); testSame(options, code); CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel( options); options.renamePrefixNamespace = "_"; test(options, code, "_.x = null; try { +_.x.FOO; } catch (e) {}"); } public void testRenamePrefixNamespaceActivatesMoveFunctionDeclarations() { CompilerOptions options = createCompilerOptions(); String code = "var x = f; function f() { return 3; }"; testSame(options, code); assertFalse(options.moveFunctionDeclarations); options.renamePrefixNamespace = "_"; test(options, code, "_.f = function() { return 3; }; _.x = _.f;"); } public void testBrokenNameSpace() { CompilerOptions options = createCompilerOptions(); String code = "var goog; goog.provide('i.am.on.a.Horse');" + "i.am.on.a.Horse = function() {};" + "i.am.on.a.Horse.prototype.x = function() {};" + "i.am.on.a.Boat.prototype.y = function() {}"; options.closurePass = true; options.collapseProperties = true; options.smartNameRemoval = true; test(options, code, ""); } public void testNamelessParameter() { CompilerOptions options = createCompilerOptions(); CompilationLevel.ADVANCED_OPTIMIZATIONS .setOptionsForCompilationLevel(options); String code = "var impl_0;" + "$load($init());" + "function $load(){" + " window['f'] = impl_0;" + "}" + "function $init() {" + " impl_0 = {};" + "}"; String result = "window.f = {};"; test(options, code, result); } public void testHiddenSideEffect() { CompilerOptions options = createCompilerOptions(); CompilationLevel.ADVANCED_OPTIMIZATIONS .setOptionsForCompilationLevel(options); options.setAliasExternals(true); String code = "window.offsetWidth;"; String result = "window.offsetWidth;"; test(options, code, result); } public void testNegativeZero() { CompilerOptions options = createCompilerOptions(); CompilationLevel.ADVANCED_OPTIMIZATIONS .setOptionsForCompilationLevel(options); test(options, "function bar(x) { return x; }\n" + "function foo(x) { print(x / bar(0));\n" + " print(x / bar(-0)); }\n" + "foo(3);", "print(3/0);print(3/-0);"); } public void testSingletonGetter1() { CompilerOptions options = createCompilerOptions(); CompilationLevel.ADVANCED_OPTIMIZATIONS .setOptionsForCompilationLevel(options); options.setCodingConvention(new ClosureCodingConvention()); test(options, "/** @const */\n" + "var goog = goog || {};\n" + "goog.addSingletonGetter = function(ctor) {\n" + " ctor.getInstance = function() {\n" + " return ctor.instance_ || (ctor.instance_ = new ctor());\n" + " };\n" + "};" + "function Foo() {}\n" + "goog.addSingletonGetter(Foo);" + "Foo.prototype.bar = 1;" + "function Bar() {}\n" + "goog.addSingletonGetter(Bar);" + "Bar.prototype.bar = 1;", ""); } public void testIncompleteFunction1() { CompilerOptions options = createCompilerOptions(); options.ideMode = true; DiagnosticType[] warnings = new DiagnosticType[]{ RhinoErrorReporter.PARSE_ERROR, RhinoErrorReporter.PARSE_ERROR}; test(options, new String[] { "var foo = {bar: function(e) }" }, new String[] { "var foo = {bar: function(e){}};" }, warnings ); } public void testIncompleteFunction2() { CompilerOptions options = createCompilerOptions(); options.ideMode = true; DiagnosticType[] warnings = new DiagnosticType[]{ RhinoErrorReporter.PARSE_ERROR, RhinoErrorReporter.PARSE_ERROR, RhinoErrorReporter.PARSE_ERROR, RhinoErrorReporter.PARSE_ERROR, RhinoErrorReporter.PARSE_ERROR, RhinoErrorReporter.PARSE_ERROR}; test(options, new String[] { "function hi" }, new String[] { "function hi() {}" }, warnings ); } public void testSortingOff() { CompilerOptions options = new CompilerOptions(); options.closurePass = true; options.setCodingConvention(new ClosureCodingConvention()); test(options, new String[] { "goog.require('goog.beer');", "goog.provide('goog.beer');" }, ProcessClosurePrimitives.LATE_PROVIDE_ERROR); } public void testUnboundedArrayLiteralInfiniteLoop() { CompilerOptions options = createCompilerOptions(); options.ideMode = true; test(options, "var x = [1, 2", "var x = [1, 2]", RhinoErrorReporter.PARSE_ERROR); } public void testProvideRequireSameFile() throws Exception { CompilerOptions options = createCompilerOptions(); options.setDependencyOptions( new DependencyOptions() .setDependencySorting(true)); options.closurePass = true; test( options, "goog.provide('x');\ngoog.require('x');", "var x = {};"); } public void testDependencySorting() throws Exception { CompilerOptions options = createCompilerOptions(); options.setDependencyOptions( new DependencyOptions() .setDependencySorting(true)); test( options, new String[] { "goog.require('x');", "goog.provide('x');", }, new String[] { "goog.provide('x');", "goog.require('x');", // For complicated reasons involving modules, // the compiler creates a synthetic source file. "", }); } public void testStrictWarningsGuard() throws Exception { CompilerOptions options = createCompilerOptions(); options.checkTypes = true; options.addWarningsGuard(new StrictWarningsGuard()); Compiler compiler = compile(options, "/** @return {number} */ function f() { return true; }"); assertEquals(1, compiler.getErrors().length); assertEquals(0, compiler.getWarnings().length); } public void testStrictWarningsGuardEmergencyMode() throws Exception { CompilerOptions options = createCompilerOptions(); options.checkTypes = true; options.addWarningsGuard(new StrictWarningsGuard()); options.useEmergencyFailSafe(); Compiler compiler = compile(options, "/** @return {number} */ function f() { return true; }"); assertEquals(0, compiler.getErrors().length); assertEquals(1, compiler.getWarnings().length); } public void testInlineProperties() { CompilerOptions options = createCompilerOptions(); CompilationLevel level = CompilationLevel.ADVANCED_OPTIMIZATIONS; level.setOptionsForCompilationLevel(options); level.setTypeBasedOptimizationOptions(options); String code = "" + "var ns = {};\n" + "/** @constructor */\n" + "ns.C = function () {this.someProperty = 1}\n" + "alert(new ns.C().someProperty + new ns.C().someProperty);\n"; assertTrue(options.inlineProperties); assertTrue(options.collapseProperties); // CollapseProperties used to prevent inlining this property. test(options, code, "alert(2);"); } public void testGoogDefineClass1() { CompilerOptions options = createCompilerOptions(); CompilationLevel level = CompilationLevel.ADVANCED_OPTIMIZATIONS; level.setOptionsForCompilationLevel(options); level.setTypeBasedOptimizationOptions(options); String code = "" + "var ns = {};\n" + "ns.C = goog.defineClass(null, {\n" + " /** @constructor */\n" + " constructor: function () {this.someProperty = 1}\n" + "});\n" + "alert(new ns.C().someProperty + new ns.C().someProperty);\n"; assertTrue(options.inlineProperties); assertTrue(options.collapseProperties); // CollapseProperties used to prevent inlining this property. test(options, code, "alert(2);"); } public void testGoogDefineClass2() { CompilerOptions options = createCompilerOptions(); CompilationLevel level = CompilationLevel.ADVANCED_OPTIMIZATIONS; level.setOptionsForCompilationLevel(options); level.setTypeBasedOptimizationOptions(options); String code = "" + "var C = goog.defineClass(null, {\n" + " /** @constructor */\n" + " constructor: function () {this.someProperty = 1}\n" + "});\n" + "alert(new C().someProperty + new C().someProperty);\n"; assertTrue(options.inlineProperties); assertTrue(options.collapseProperties); // CollapseProperties used to prevent inlining this property. test(options, code, "alert(2);"); } public void testGoogDefineClass3() { CompilerOptions options = createCompilerOptions(); CompilationLevel level = CompilationLevel.ADVANCED_OPTIMIZATIONS; level.setOptionsForCompilationLevel(options); level.setTypeBasedOptimizationOptions(options); WarningLevel warnings = WarningLevel.VERBOSE; warnings.setOptionsForWarningLevel(options); String code = "" + "var C = goog.defineClass(null, {\n" + " /** @constructor */\n" + " constructor: function () {\n" + " /** @type {number} */\n" + " this.someProperty = 1},\n" + " /** @param {string} a */\n" + " someMethod: function (a) {}\n" + "});" + "var x = new C();\n" + "x.someMethod(x.someProperty);\n"; assertTrue(options.inlineProperties); assertTrue(options.collapseProperties); // CollapseProperties used to prevent inlining this property. test(options, code, TypeValidator.TYPE_MISMATCH_WARNING); } public void testCheckConstants1() { CompilerOptions options = createCompilerOptions(); CompilationLevel level = CompilationLevel.SIMPLE_OPTIMIZATIONS; level.setOptionsForCompilationLevel(options); WarningLevel warnings = WarningLevel.QUIET; warnings.setOptionsForWarningLevel(options); String code = "" + "var foo; foo();\n" + "/** @const */\n" + "var x = 1; foo(); x = 2;\n"; test(options, code, code); } public void testCheckConstants2() { CompilerOptions options = createCompilerOptions(); CompilationLevel level = CompilationLevel.SIMPLE_OPTIMIZATIONS; level.setOptionsForCompilationLevel(options); WarningLevel warnings = WarningLevel.DEFAULT; warnings.setOptionsForWarningLevel(options); String code = "" + "var foo;\n" + "/** @const */\n" + "var x = 1; foo(); x = 2;\n"; test(options, code, ConstCheck.CONST_REASSIGNED_VALUE_ERROR); } public void testIssue787() { CompilerOptions options = createCompilerOptions(); CompilationLevel level = CompilationLevel.SIMPLE_OPTIMIZATIONS; level.setOptionsForCompilationLevel(options); WarningLevel warnings = WarningLevel.DEFAULT; warnings.setOptionsForWarningLevel(options); String code = "" + "function some_function() {\n" + " var fn1;\n" + " var fn2;\n" + "\n" + " if (any_expression) {\n" + " fn2 = external_ref;\n" + " fn1 = function (content) {\n" + " return fn2();\n" + " }\n" + " }\n" + "\n" + " return {\n" + " method1: function () {\n" + " if (fn1) fn1();\n" + " return true;\n" + " },\n" + " method2: function () {\n" + " return false;\n" + " }\n" + " }\n" + "}"; String result = "" + "function some_function() {\n" + " var a, b;\n" + " any_expression && (b = external_ref, a = function(a) {\n" + " return b()\n" + " });\n" + " return{method1:function() {\n" + " a && a();\n" + " return !0\n" + " }, method2:function() {\n" + " return !1\n" + " }}\n" + "}\n" + ""; test(options, code, result); } public void testManyAdds() { CompilerOptions options = createCompilerOptions(); CompilationLevel level = CompilationLevel.SIMPLE_OPTIMIZATIONS; level.setOptionsForCompilationLevel(options); WarningLevel warnings = WarningLevel.VERBOSE; warnings.setOptionsForWarningLevel(options); int numAdds = 4750; StringBuilder original = new StringBuilder("var x = 0"); for (int i = 0; i < numAdds; i++) { original.append(" + 1"); } original.append(";"); test(options, original.toString(), "var x = " + numAdds + ";"); } /** Creates a CompilerOptions object with google coding conventions. */ @Override protected CompilerOptions createCompilerOptions() { CompilerOptions options = new CompilerOptions(); options.setCodingConvention(new GoogleCodingConvention()); return options; } }