/* * Copyright 2011 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import static com.google.common.truth.Truth.assertThat; import static com.google.javascript.jscomp.testing.NodeSubject.assertNode; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.ObjectType; import com.google.javascript.rhino.jstype.StaticTypedSlot; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; /** * Various tests for {@code replaceScript} functionality of Closure Compiler. * * @author bashir@google.com (Bashir Sadjad) */ public final class SimpleReplaceScriptTest extends BaseReplaceScriptTestCase { public void testInfer() { CompilerOptions options = getOptions(DiagnosticGroups.ACCESS_CONTROLS); String source = "" + "var obj = {};\n" + "/** @param {number} n */\n" + "obj.temp = function(n) {this.num = n;};\n" + "obj.temp(10);\n"; Result result = runReplaceScript(options, ImmutableList.of(source), 0, 0, source, 0, false).getResult(); assertTrue(result.success); } public void testInferWithModules() { CompilerOptions options = getOptions(); Compiler compiler = new Compiler(); List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("in", "")); Result result = compiler.compile(EXTERNS, inputs, options); assertTrue(result.success); CompilerInput oldInput = compiler.getInput(new InputId("in")); JSModule myModule = oldInput.getModule(); assertThat(myModule.getInputs()).hasSize(1); SourceFile newSource = SourceFile.fromCode("in", "var x;"); JsAst ast = new JsAst(newSource); compiler.replaceScript(ast); assertThat(myModule.getInputs()).hasSize(1); assertThat(myModule.getInputs()).doesNotContain(oldInput); assertThat(myModule.getInputs()).containsExactly(compiler.getInput(new InputId("in"))); } public void testreplaceScript() { CompilerOptions options = getOptions(DiagnosticGroups.ACCESS_CONTROLS); Compiler compiler = new Compiler(); String source = "" + "/** @param {number} n */\n" + "temp = function(n) {retrun (n + 1);};\n" + "temp(10);\n"; List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("in", source)); Result result = compiler.compile(EXTERNS, inputs, options); assertTrue(result.success); // Now try to re-infer with a modified version of source // with a new variable. String source2 = "" + "var a = 20;\n" + "/** @param {number} n */\n" + "temp = function(n) {retrun (n + 1);};\n" + "temp(a);\n"; SourceFile newSource = SourceFile.fromCode("in", source2); JsAst ast = new JsAst(newSource); compiler.replaceScript(ast); } public void testWithProvidesAndClosureOn() { runReplaceScriptWithProvides(true); } public void testWithProvidesAndClosureOff() { runReplaceScriptWithProvides(false); } private void runReplaceScriptWithProvides(boolean closureOn) { CompilerOptions options = getOptions(DiagnosticGroups.ACCESS_CONTROLS); options.setClosurePass(closureOn); String source = "goog.provide('Bar');" + "/** @constructor */ Bar = function() {};"; // A modified version of source String newSource = "goog.provide('Bar');\ngoog.provide('Baz');" + "/** @constructor */ Bar = function() {};\n" + "/** @constructor */ Baz = function() {};"; Result result = this.runReplaceScript(options, ImmutableList.of( CLOSURE_BASE, source), 0, 0, newSource, 1, false).getResult(); assertNoWarningsOrErrors(result); } /** Test related to DefaultPassConfig.checkTypes */ public void testUndefinedVars() { // Setting options for checking variables. CompilerOptions options = getOptions(DiagnosticGroups.CHECK_VARIABLES); // We need to set checkSymbols otherwise Compiler.initOptions will turn // CHECK_VARIABLES warnings off. options.setCheckSymbols(true); String firstSource = "var aVar = 10;"; // Note only bVar is undefined because aVar is defined in firstSource. String secondSource = "var n = aVar;\n" + "var b = bVar + 1;"; // Run replaceScript on second with new undefined-var errors. // Note there should be no error on aVar but two on bVar and cVar. String modifiedSource = "var n = aVar;\n" + "var b = bVar + 1;\n" + "var c = cVar + 1;"; Result result = this.runReplaceScript(options, ImmutableList.of( firstSource, secondSource), 1, 0, modifiedSource, 1, true).getResult(); assertFalse(result.success); assertThat(result.errors).hasLength(2); int i = 2; for (JSError e : result.errors) { assertErrorType(e, VarCheck.UNDEFINED_VAR_ERROR, i++); } } /** Test related to DefaultPassConfig.checkVariableReferences */ public Compiler runRedefinedVarsTest(List<String> sources, int numOrigError, String newSrc, int newSrcInd, List<Integer> errorLineNumbers) { CompilerOptions options = getOptions(); options.setCheckSymbols(true); options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.ERROR); return this.runReplaceScript(options, sources, numOrigError, 0, newSrc, newSrcInd, true); } /** Test related to DefaultPassConfig.checkVariableReferences */ public void testRedefinedVars() { String src = "var a = 10;\n var a = 20;"; runRedefinedVarsTest(ImmutableList.of(src), 1, src, 0, ImmutableList.of(2)); } public void testReferToExternVar() { String src = "var foo = extVar;"; List<Integer> errorLines = new ArrayList<>(); runRedefinedVarsTest(ImmutableList.of(src), 0, src, 0, errorLines); } /** Test for DefaultPassConfig.checkVariableReferences with two files */ public void testRedefinedVarsTwoFiles() { String src0 = "var a = 10; \n var b = 11;"; String src1 = "var a = 20;"; runRedefinedVarsTest(ImmutableList.of(src0, src1), 1, src1, 1, ImmutableList.of(1)); String modifiedSrc1 = "var c = 22; \n var b = 21;"; runRedefinedVarsTest(ImmutableList.of(src0, src1), 1, modifiedSrc1, 1, ImmutableList.of(2)); } /** * Test for DefaultPassConfig.checkVariableReferences with multiple files * where changes in one file causes errors in a down-stream file. */ public void testRedefinedVarsMultipleFiles() { String src0 = "var a = 10;\n var b = 11;"; String src1 = "var a = 20;\n var d = 23;"; String src2 = "var c = 32;\n var d = 23;"; String modifiedSrc1 = "var c = 20;\n var b = 20;"; // Note replaceScript reports errors on files other than modified (expected) Compiler compiler = runRedefinedVarsTest(ImmutableList.of(src0, src1, src2), 2, modifiedSrc1, 1, ImmutableList.of(2, 1)); // Now for src2, no errors should be reported on d but one on c. flushResults(compiler); doReplaceScript(compiler, src2, 2); Result result = compiler.getResult(); assertThat(result.errors).hasLength(3); assertErrorType(result.errors[1], VarCheck.VAR_MULTIPLY_DECLARED_ERROR, 1); } /** * Test for DefaultPassConfig.checkVariableReferences with multiple files * and with multiple add/remove for same variable in different files. */ public void testRedefinedVarsMultipleChangesForOneVar() { String src0 = "var a = 10;\n var b = 11;"; String src1 = "var b = 20;\n"; String src2 = "var b = 20;\n var c = 22;"; // Note replaceScript reports errors on files other than modified (expected) Compiler compiler = runRedefinedVarsTest(ImmutableList.of(src0, src1, src2), 2, src0, 0, ImmutableList.of(1, 1)); String modifiedSrc0 = "var a = 10;\n"; flushResults(compiler); doReplaceScript(compiler, modifiedSrc0, 0); // Now for src2, one error should be reported on b. flushResults(compiler); doReplaceScript(compiler, src2, 2); Result result = compiler.getResult(); assertThat(result.errors).hasLength(2); assertErrorType(result.errors[0], VarCheck.VAR_MULTIPLY_DECLARED_ERROR, 1); } /** * Test related to DefaultPassConfig.checkVariableReferences where no error * is expected (note same variable names in two scopes). */ public void testRedefinedVarsFunction() { String src0 = "var a = 10;\n var b = 10;"; String src1 = "var a = 20;"; String modifiedSrc1 = "function test() { var a = 20; }\n var b = 20;"; // Note some of the errors in replaceScript would be on src2. runRedefinedVarsTest(ImmutableList.of(src0, src1), 1, modifiedSrc1, 1, ImmutableList.of(2)); } /** * Undefined vars are added to {@code VarCheck.SYNTHETIC_VARS_DECLAR} and * previously this input was not properly added to list of externs which was * causing an NPE in hot-swap mode of {@code ReferenceCollectingCallback}. */ public void testAccessToUndefinedVar() { String src = "/** \n @fileoverview \n @suppress {checkVars} */ var a = undefVar;\n"; List<Integer> errorLines = new ArrayList<>(); runRedefinedVarsTest(ImmutableList.of(src), 0, src, 0, errorLines); } public void testParseErrorDoesntCrashCompilation() { CompilerOptions options = getOptions(); Compiler compiler = new Compiler(); // Regression test for b/30957755. List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("in", "bad!()")); try { compiler.compile(EXTERNS, inputs, options); } catch (RuntimeException e) { fail("replaceScript threw a RuntimeException on a parse error."); } } /** * Test that two previously common problems don't happen in inc-compile: * (i) require is not defined on goog * (ii) "undefined has no properties" on the instantiation of ns.Bar(). * See the usage in test functions below. */ private void checkProvideRequireErrors(CompilerOptions options) { String source0 = "goog.provide('ns.Bar');\n" + "/** @constructor */ ns.Bar = function() {};"; String source1 = "goog.require('ns.Bar');\n" + "var a = new ns.Bar();"; Result result = runReplaceScript(options, ImmutableList.of(source0, source1), 0, 0, source1, 1, false).getResult(); assertNoWarningsOrErrors(result); } public void testProvideRequireErrors() { CompilerOptions options = getOptions(DiagnosticGroups.MISSING_PROPERTIES); checkProvideRequireErrors(options); } public void testClassInstantiation() { CompilerOptions options = getOptions(DiagnosticGroups.CHECK_TYPES); checkProvideRequireErrors(options); } public void testCheckRequires() { CompilerOptions options = getOptions(); options.setWarningLevel(DiagnosticGroups.MISSING_REQUIRE, CheckLevel.ERROR); // Note it needs declaration of ns to throw the error because closurePass // which replaces goog.provide happens afterwards (see checkRequires pass). String source0 = "var ns = {};\n goog.provide('ns.Bar');\n" + "/** @constructor */ ns.Bar = function() {};"; String source1 = "var a = new ns.Bar();"; Result result = runReplaceScript(options, ImmutableList.of(source0, source1), 1, 0, source1, 1, true) .getResult(); // TODO(joeltine): Change back to asserting an error when b/28869281 // is fixed. assertTrue(result.success); } public void testCheckRequiresWithNewVar() { CompilerOptions options = getOptions(); options.setWarningLevel(DiagnosticGroups.MISSING_REQUIRE, CheckLevel.ERROR); String src = ""; String modifiedSrc = src + "\n(function() { var a = new ns.Bar(); })();"; Result result = runReplaceScript(options, ImmutableList.of(src), 0, 0, modifiedSrc, 0, false).getResult(); // TODO(joeltine): Change back to asserting an error when b/28869281 // is fixed. assertTrue(result.success); } public void testCheckProvides() { CompilerOptions options = getOptions(); options.setWarningLevel(DiagnosticGroups.MISSING_PROVIDE, CheckLevel.ERROR); checkProvideRequireErrors(options); String source0 = "goog.provide('ns.Foo'); /** @constructor */ ns.Foo = function() {};" + "/** @constructor */ ns.Bar = function() {};"; Result result = runReplaceScript(options, ImmutableList.of(source0), 1, 0, source0, 0, true).getResult(); assertFalse(result.success); assertThat(result.errors).hasLength(1); assertErrorType(result.errors[0], CheckProvides.MISSING_PROVIDE_WARNING, 1); } /** Test related to DefaultPassConfig.inferTypes */ public void testNewTypeAdded() { CompilerOptions options = getOptions(DiagnosticGroups.CHECK_TYPES); String src = "/** @constructor */\n" + "Bar = function() {};\n" // TODO(bashir) Why the error goes away by adding /**@type {Bar}*/ here? + "var a = new Bar();\n"; String modifiedSrc = src + "var b = a * 20;"; Result result = this.runReplaceScript(options, ImmutableList.of(src), 0, 0, modifiedSrc, 0, false).getResult(); assertFalse(result.success); assertThat(result.errors).hasLength(1); assertErrorType(result.errors[0], TypeValidator.TYPE_MISMATCH_WARNING, 4); assertThat(result.warnings).isEmpty(); } public void testProvidedTypeDef() { CompilerOptions options = getOptions(); String src1 = LINE_JOINER.join( "goog.provide('foo.Cat');", "goog.provide('foo.Bar');", "/** @typedef {!Array<string>} */", "foo.Bar;", "foo.Cat={};"); String src2 = "goog.require('foo.Cat');\ngoog.require('foo.Bar');"; Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, src1, src2), 0, 0, src2, 2, false); assertNoWarningsOrErrors(compiler.getResult()); } public void testDeclarationMoved() { CompilerOptions options = getOptions(); String srcPrefix = LINE_JOINER.join( "goog.provide('Bar');", "/** @constructor */", "Bar = function() {};"); String declaration = "Bar.foo = function() {};"; String originalSrc = srcPrefix + declaration; String modifiedSrc = srcPrefix + "\n\n\n\n" + declaration; Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, originalSrc), 0, 0, modifiedSrc, 1, false); assertNoWarningsOrErrors(compiler.getResult()); verifyPropertyLineno(compiler, "Bar", "foo", 7); } public void testTypeDefRedeclaration() { // Tests that replacing/redeclaring a @typedef can be replaced via replaceScript. CompilerOptions options = getOptions(); String originalSrc = "/** @typedef {number} */ var Foo;"; Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, originalSrc), 0, 0, originalSrc, 1, false); assertNoWarningsOrErrors(compiler.getResult()); } public void testConstructorDeclarationRedefined() { // Tests that redefining a @constructor does not fail when using replaceScript. Regression // test for b/28939919. CompilerOptions options = getOptions(); String originalSrc = LINE_JOINER.join( "goog.provide('Bar');", "/** @constructor */", "Bar = function() {};"); String modifiedSrc = LINE_JOINER.join( "goog.provide('Bar');", "/**", " * @constructor", " * @param {string} s", " */", "Bar = function(s) {};"); Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, originalSrc), 0, 0, modifiedSrc, 1, false); assertNoWarningsOrErrors(compiler.getResult()); } public void testDeclarationInAnotherFile() { CompilerOptions options = getOptions(); String src = LINE_JOINER.join( "goog.provide('ns.Bar');", "/** @constructor */", "ns.Bar = function() {};"); String otherSrc = LINE_JOINER.join( "goog.require('ns.Bar');", "ns.Bar.foo = function() {};"); Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, src, otherSrc), 0, 0, src, 1, false); assertNoWarningsOrErrors(compiler.getResult()); // Considering the "ns.Bar" type is deleted in the compilation above, the property "foo" is only // updated after the file defining it is replaced. doReplaceScript(compiler, otherSrc, 2); assertNoWarningsOrErrors(compiler.getResult()); verifyPropertyLineno(compiler, "ns.Bar", "foo", 2); } public void testRedeclarationOfStructProperties() { // Tests that definition of a property on a @struct does not fail on replaceScript. A regression // test for b/28940462. CompilerOptions options = getOptions(); String src = LINE_JOINER.join( "goog.provide('ns.Bar');", "/**", " * @constructor", " * @struct", " */", "ns.Bar = function() {};", "/** @private */", "ns.Bar.r_ = {};"); Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, src), 0, 0, src, 1, false); assertNoWarningsOrErrors(compiler.getResult()); } public void testInterfaceOverrideDeclarations() { // Tests that incremental compilation of a class implementing an interface does not fail // on replaceScript. Regression test for b/28942209. CompilerOptions options = getOptions(); String src = LINE_JOINER.join( "goog.provide('ns.IBar');", "/** @interface */", "ns.IBar = function() {};", "/** @return {boolean} */", "ns.IBar.prototype.x = function() {};", "/**", " * @private", " * @constructor", " * @implements {ns.IBar} */", "ns.Bar_ = function() {};", "/** @override */", "ns.Bar_.prototype.x = function() {return true};"); Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, src), 0, 0, src, 1, false); assertNoWarningsOrErrors(compiler.getResult()); } public void testAssignmentToConstProperty() { // Tests that defining a field on a @const property does not fail with incorrect // "assignment to property" error. Regression test for b/28981397. CompilerOptions options = getOptions(); String src = LINE_JOINER.join( "goog.provide('ns.A');", "/** @constructor */", "ns.A = function() {", " /**", " * @const @private", " */", " this.b = {};", " this.b.ANY = 'FOO';", "};"); Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, src), 0, 0, src, 1, false); assertNoWarningsOrErrors(compiler.getResult()); } public void testDeclarationOverride() { CompilerOptions options = getOptions(); String src1 = "goog.provide('ns.Bar');\n" + "/** @constructor */\n" + "ns.Bar = function() {};\n" + "ns.Bar.temp = function() {};\n" + "ns.Bar.func = function() {};\n"; String src2 = "goog.provide('ns.Foo');\n" + "goog.require('ns.Bar');\n" + "/**\n" + " * @extends {ns.Bar}\n" + " * @constructor\n" + " */\n" + "ns.Foo = function() {};\n" + "goog.inherits(ns.Foo, ns.Bar);\n" + "/** @override */\n" + "ns.Foo.func = function() {" + " ns.Bar.temp(); " + "};\n"; String newSrc2 = "\n\n\n" + src2; Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, src1, src2), 0, 0, newSrc2, 2, false); assertNoWarningsOrErrors(compiler.getResult()); verifyPropertyLineno(compiler, "ns.Foo", "func", 13); doReplaceScript(compiler, src1, 1); verifyPropertyLineno(compiler, "ns.Foo", "func", 13); } public void testDeclarationWithThisMoved() { CompilerOptions options = getOptions(); String src1 = "goog.provide('ns.Bar');\n" + "/** @constructor */\n" + "ns.Bar = function() {\n" + " this.temp = 10;\n" + "};\n"; String src2 = "goog.require('ns.Bar');\n" + "/** @type {!ns.Bar} */\n" + "var test = new ns.Bar();\n"; String modifiedSrc1 = "\n\n\n\n" + src1; Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, src1, src2), 0, 0, modifiedSrc1, 1, false); assertNoWarningsOrErrors(compiler.getResult()); // The new property lineno is only picked up after recompiling the file where "test" is defined. doReplaceScript(compiler, src2, 2); assertNoWarningsOrErrors(compiler.getResult()); verifyPropertyLineno(compiler, "test", "temp", 8); } public void testDeclarationOtherTypeWithField() { CompilerOptions options = getOptions(); String srcPrefix = "goog.provide('Bar');\n" + "/** @constructor */\n" + "Bar = function() {};\n"; String declaration = "Bar.foo = function() {};\n"; String originalSrc = srcPrefix + declaration; String modifiedSrc = srcPrefix + "\n\n\n\n" + declaration; String otherSrc = "goog.provide('Baz');\n" + "/** @constructor */\n" + "Baz = function() {};\n" + "Baz.foo = function() {};\n"; Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, originalSrc, otherSrc), 0, 0, modifiedSrc, 1, false); assertNoWarningsOrErrors(compiler.getResult()); verifyPropertyLineno(compiler, "Bar", "foo", 8); verifyPropertyLineno(compiler, "Baz", "foo", 4); } public void testDeclarationInGoogScopeMoved() { CompilerOptions options = getOptions(); String src1 = "/** @constructor */\n" + "test.Bar = function() { this.privNum = 10; };\n"; String src2 = "goog.scope(function() {\n" + " var Bar = test.Bar;\n" + " Bar.temp = 10;" + "});"; String modifiedSrc2 = "\n\n\n\n" + src2; Compiler compiler = runReplaceScript(options, ImmutableList.of(CLOSURE_BASE, src1, src2), 0, 0, modifiedSrc2, 2, false); assertNoWarningsOrErrors(compiler.getResult()); verifyPropertyLineno(compiler, "test.Bar", "temp", 7); } private void verifyPropertyLineno(Compiler compiler, String varName, String propName, int expectedLineno) { TypedVar var = compiler.getTopScope().getVar(varName); ObjectType objType = var.getType().toObjectType(); Node propNode = objType.getPropertyNode(propName); assertThat(propNode).isNotNull(); assertNode(propNode).hasLineno(expectedLineno); } public void testGlobalVarDeclarationMoved() { CompilerOptions options = getOptions(); String prefix = "var a = 3;\n"; String declaration = "var b = 4;\n"; String src = prefix + declaration; String newSrc = prefix + "\n\n\n\n" + declaration; Compiler compiler = runReplaceScript( options, ImmutableList.of(CLOSURE_BASE, src), 0, 0, newSrc, 1, false); assertNoWarningsOrErrors(compiler.getResult()); TypedVar var = compiler.getTopScope().getVar("b"); assertNode(var.getNode()).hasLineno(6); } public void testNamespaceTypeInference() { CompilerOptions options = getOptions(DiagnosticGroups.CHECK_TYPES); String decl = "goog.provide('ns.Bar');\n" + "/** @constructor */ ns.Bar = function() {};"; String ref = "goog.require('ns.Bar');\n" + "var x = new ns.Bar();"; Result result = runReplaceScript(options, ImmutableList.of( CLOSURE_BASE, decl, ref), 0, 0, ref, 2, false).getResult(); assertNoWarningsOrErrors(result); } public void testSourceNodeOfFunctionTypesUpdated() { String provideSrc = "goog.provide('ns.Foo');\n"; String mainSrc = "/** @constructor */\n" + "ns.Foo = function() {\n" + "};\n" + "ns.Foo.prototype.fn = function(val){\n" + " return 'abc';\n" + "};\n"; String newSource = provideSrc + "\n\n\n" + mainSrc; String src = provideSrc + mainSrc; Compiler compiler = runReplaceScript(getOptions(), ImmutableList.of(CLOSURE_BASE, src), 0, 0, newSource, 1, false); Result result = compiler.getResult(); assertNoWarningsOrErrors(result); JSType type = compiler.getTypeIRegistry().getType("ns.Foo"); FunctionType fnType = type.toObjectType().getConstructor(); Node srcNode = fnType.getSource(); assertNode(srcNode).hasLineno(6); } public void testAssociatedNodeOfJsDocNotLeaked() { String src = "goog.provide('ns.Foo');\n" + "/** @constructor */\n" + "ns.Foo = function() {\n" + "};\n" + "/**\n" + " * @param {number} val \n" + " * @return {string} \n" + " */\n" + "ns.Foo.prototype.fn = function(val){\n" + " return 'abc';\n" + "};\n"; Compiler compiler = runFullCompile( getOptions(), ImmutableList.of(CLOSURE_BASE, src), 0, 0, false); assertNoWarningsOrErrors(compiler.getResult()); doReplaceScript(compiler, src, 1); assertNoWarningsOrErrors(compiler.getResult()); } public void testFunctionAssignedToAnotherFunction() { String src2 = "goog.provide('ns.Bar');\n" + "/** @return {null} */\n" + "ns.fn = function() {};\n"; String src = "goog.provide('ns.Foo');\n" + "goog.require('ns.Bar');\n" + "/** @constructor */\n" + "ns.Foo = function() {\n" + " this.fn();" + "};\n" + "/**\n" + " * Performs feature-specific initialization.\n" + " * @protected\n" + " */\n" + "ns.Foo.prototype.fn = ns.fn;\n"; CompilerOptions options = getOptions(); options.setCheckTypes(true); Compiler compiler = runFullCompile(options, ImmutableList.of(CLOSURE_BASE, src2, src), 0, 0, false); assertNoWarningsOrErrors(compiler.getResult()); doReplaceScript(compiler, src, 2); assertNoWarningsOrErrors(compiler.getResult()); } public void testPrototypeSlotChangedOnCompile() { String src = "goog.provide('ns.Foo');\n" + "/** @constructor */\n" + "ns.Foo = function() {\n" + "};\n" + "ns.Foo.prototype.fn = function(val){\n" + " return 'abc';\n" + "};\n"; Compiler compiler = runFullCompile( getOptions(), ImmutableList.of(CLOSURE_BASE, src), 0, 0, false); JSType type = compiler.getTypeIRegistry().getType("ns.Foo"); FunctionType fnType = type.toObjectType().getConstructor(); StaticTypedSlot<JSType> originalSlot = fnType.getSlot("prototype"); doReplaceScript(compiler, src, 1); assertNoWarningsOrErrors(compiler.getResult()); type = compiler.getTypeIRegistry().getType("ns.Foo"); fnType = type.toObjectType().getConstructor(); StaticTypedSlot<JSType> newSlot = fnType.getSlot("prototype"); assertNotSame(originalSlot, newSlot); } /** * This test will fail if global scope generation happens before closure-pass. */ public void testGlobalScopeGenerationWithProvide() { CompilerOptions options = getOptions(); options.setCheckSymbols(true); String src = "goog.provide('namespace.Bar');\n" + "/** @constructor */ namespace.Bar = function() {};"; Result result = runReplaceScript(options, ImmutableList.of(src), 0, 0, src, 0, false).getResult(); assertNoWarningsOrErrors(result); } public void testAccessControls() { CompilerOptions options = getOptions(DiagnosticGroups.ACCESS_CONTROLS); options.setCheckTypes(true); String src0 = "/** @constructor */\n" + "test.Bar = function() { this.privNum = 10; };\n" + "/** @private */\n" + "test.Bar.prototype.privNum;\n" + "/** @protected */\n" + "test.Bar.prototype.protNum;\n"; String src1 = "var a = new test.Bar();\n" + "var b = a.privNum;\n" + "a.privNum = 20;\n" + "var c = a.protNum;\n"; Result result = this.runReplaceScript(options, ImmutableList.of(src0, src1), 3, 0, src1, 1, true).getResult(); assertNumWarningsAndErrors(result, 3, 0); assertErrorType(result.errors[0], CheckAccessControls.BAD_PRIVATE_PROPERTY_ACCESS, 2); assertErrorType(result.errors[1], CheckAccessControls.BAD_PRIVATE_PROPERTY_ACCESS, 3); assertErrorType(result.errors[2], CheckAccessControls.BAD_PROTECTED_PROPERTY_ACCESS, 4); } public void testGlobalThisCheck() { CompilerOptions options = getOptions(DiagnosticGroups.GLOBAL_THIS); String src = "/** @constructor */ namespace.Bar = function() {};\n" + "namespace.Bar.someFunc = function() { this.newField = 20; }"; Result result = runReplaceScript(options, ImmutableList.of(src), 1, 0, src, 0, true).getResult(); assertNumWarningsAndErrors(result, 1, 0); assertErrorType(result.errors[0], CheckGlobalThis.GLOBAL_THIS, 2); } public void testNoSideEffect() { CompilerOptions options = getOptions(); options.setCheckSuspiciousCode(true); options.setWarningLevel(DiagnosticGroups.ES5_STRICT, CheckLevel.OFF); String src = "var s = 'test'\n" + "'this';\n"; Result result = runReplaceScript(options, ImmutableList.of(src), 0, 1, src, 0, true).getResult(); assertNumWarningsAndErrors(result, 0, 1); assertErrorType(result.warnings[0], CheckSideEffects.USELESS_CODE_ERROR, 2); } public void testAccidentalSemicolon() { CompilerOptions options = getOptions(); options.setCheckSuspiciousCode(true); String src = "if (true) ; \n var s = 'test';\n"; Result result = runReplaceScript(options, ImmutableList.of(src), 0, 1, src, 0, true).getResult(); assertNumWarningsAndErrors(result, 0, 1); assertErrorType(result.warnings[0], CheckSuspiciousCode.SUSPICIOUS_SEMICOLON, 1); } public void testUnreachableCode() { CompilerOptions options = getOptions(); options.setWarningLevel(DiagnosticGroups.CHECK_USELESS_CODE, CheckLevel.ERROR); String src = "if (false) { \n var s = 'test';\n }"; Result result = runReplaceScript(options, ImmutableList.of(src), 1, 0, src, 0, true).getResult(); assertNumWarningsAndErrors(result, 1, 0); assertErrorType(result.errors[0], CheckUnreachableCode.UNREACHABLE_CODE, 1); } public void testMissingReturn() { CompilerOptions options = getOptions(); options.setCheckTypes(true); options.setWarningLevel(DiagnosticGroups.MISSING_RETURN, CheckLevel.ERROR); String src = "/** @return {number} */\n" + "temp = function() { var t = 20; };\n"; Result result = runReplaceScript(options, ImmutableList.of(src), 1, 0, src, 0, true).getResult(); assertNumWarningsAndErrors(result, 1, 0); assertErrorType(result.errors[0], CheckMissingReturn.MISSING_RETURN_STATEMENT, 2); } /** Test related to DefaultPassConfig.closureGoogScopeAliases */ public void testGoogScope() { // Checking a type of error to make sure goog.scope is processed. CompilerOptions options = getOptions(DiagnosticGroups.ACCESS_CONTROLS); options.setCheckTypes(true); String src0 = "/** @constructor */\n" + "test.Bar = function() { this.privNum = 10; };\n" + "/** @private */\n" + "test.Bar.prototype.privNum;\n"; String src1 = "var a = new test.Bar();\n"; String modifiedSrc1 = "goog.scope(function() {\n" + " var Bar = test.Bar;\n" + " test.test = function() {\n" + " var a = new Bar();\n" + " var b = a.privNum;\n" + " };" + "});"; Result result = this.runReplaceScript(options, ImmutableList.of(src0, src1), 0, 0, modifiedSrc1, 1, true).getResult(); //ImmutableList.of(src0, modifiedSrc1), 1, 0, modifiedSrc1, 1, true); assertFalse(result.success); assertThat(result.errors).hasLength(1); assertErrorType(result.errors[0], CheckAccessControls.BAD_PRIVATE_PROPERTY_ACCESS, 5); } /** * Test related to PassConfig.patchGlobalTypedScope. * First it generates the global typed scope in a normal full compile. Then * with no modifications calls patchGlobalTypedScope on one of the scripts and * compare the results to full-compile. Then changes one script and checks * the results again. */ public void testPatchGlobalTypedScope() { CompilerOptions options = getOptions(DiagnosticGroups.CHECK_TYPES); String externSrc = "/** @type {number} */ var aNum;\n"; String src1 = "goog.provide('unique.Bar');\n" + "/** @constructor */ unique.Bar = function() {};\n" + "/** @type {unique.Bar} */ var obj1 = new unique.Bar();\n" + "var testNum = 20;\n" + "var objNoType1 = new unique.Bar();\n"; String src2 = "goog.require('unique.Bar');\n" + "/** @type {unique.Bar} */ var obj2 = new unique.Bar();\n" + "var objNoType2 = new unique.Bar();"; List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("in1", src1), SourceFile.fromCode("in2", src2)); List<SourceFile> externs = ImmutableList.of( SourceFile.fromCode("extern", externSrc)); Compiler compiler = new Compiler(); Compiler.setLoggingLevel(Level.INFO); compiler.compile(externs, inputs, options); assertThat(compiler.getResult().success).isTrue(); TypedScope oldGlobalScope = compiler.getTopScope(); SourceFile newSource1 = SourceFile.fromCode("in1", src1); JsAst ast = new JsAst(newSource1); compiler.replaceScript(ast); assertTrue(compiler.getResult().success); assertScopesSimilar(oldGlobalScope, compiler.getTopScope()); assertScopeAndThisForScopeSimilar(compiler.getTopScope()); SourceFile newSource2 = SourceFile.fromCode("in2", src2); ast = new JsAst(newSource2); compiler.replaceScript(ast); assertTrue(compiler.getResult().success); assertScopesSimilar(oldGlobalScope, compiler.getTopScope()); assertScopeAndThisForScopeSimilar(compiler.getTopScope()); newSource2 = SourceFile.fromCode("in2", ""); ast = new JsAst(newSource2); compiler.replaceScript(ast); assertTrue(compiler.getResult().success); assertSubsetScope(compiler.getTopScope(), oldGlobalScope, ImmutableSet.of("obj2", "objNoType2")); assertScopeAndThisForScopeSimilar(compiler.getTopScope()); } private void assertScopeAndThisForScopeSimilar(TypedScope scope) { ObjectType typeOfThis = scope.getTypeOfThis().toObjectType(); for (TypedVar v : scope.getAllSymbols()) { if (!v.getName().contains(".")) { assertEquals(v.getNameNode(), typeOfThis.getPropertyNode(v.getName())); } } } private void assertScopesSimilar(TypedScope scope1, TypedScope scope2) { assertSubsetScope(scope1, scope2, new HashSet<String>()); } private void assertSubsetScope(TypedScope subScope, TypedScope scope, Set<String> missingVars) { for (TypedVar var1 : scope.getVarIterable()) { TypedVar var2 = subScope.getVar(var1.getName()); if (missingVars.contains(var1.getName())) { assertNull(var2); } else { assertNotNull(var2); assertEquals(var1.getType(), var2.getType()); } } } public void testInferJsDocInfo() { CompilerOptions options = getOptions(); options.inferTypes = true; String src = ""; String modifiedSrc = "/** @constructor */\n" + "Foo = function() {};\n" + "/** @type {number} */\n" + "Foo.prototype.prop = 10;\n" + "var temp = new Foo();"; Compiler compiler = runReplaceScript(options, ImmutableList.of(src), 0, 0, modifiedSrc, 0, true); TypedVar var = compiler.getTopScope().getVar("temp"); ObjectType type = var.getType().toObjectType().getImplicitPrototype(); assertNotNull(type.getOwnPropertyJSDocInfo("prop")); } /** Effectively this tests the clean-up of properties on un-named objects. */ public void testNoErrorOnGoogProvide() { CompilerOptions options = getOptions(DiagnosticGroups.CHECK_TYPES); String src0 = "goog.provide('ns.Foo')\n" + "ns.Foo = function() {};\n"; String src1 = "goog.provide('ns.Bar')\n" + "ns.Bar.bar = function() {};\n"; Result result = this.runReplaceScript(options, ImmutableList.of(src0, src1), 0, 0, src1, 1, false).getResult(); assertTrue(result.success); assertThat(result.errors).isEmpty(); assertThat(result.warnings).isEmpty(); } public void testAddSimpleScript() { CompilerOptions options = getOptions(); options.setClosurePass(false); String src = "goog.provide('Bar');\n" + "/** @constructor */\n" + "Bar = function() {};\n"; String otherSrc = "goog.require('Bar');\n" + "Bar.foo = function() {};\n"; Compiler compiler = runAddScript(options, ImmutableList.of(CLOSURE_BASE, src), 0, 0, otherSrc, false); assertNoWarningsOrErrors(compiler.getResult()); verifyPropertyLineno(compiler, "Bar", "foo", 2); } public void testAddExistingScript() { CompilerOptions options = getOptions(); String src = "goog.provide('Bar');\n" + "/** @constructor */\n" + "Bar = function() {};\n"; String otherSrc = "goog.require('Bar');\n" + "Bar.foo = function() {};\n"; String updatedOtherSrc = "goog.require('Bar');\n" + "\n\n" + "Bar.foo = function() {};\n"; Compiler compiler = runAddScript( options, ImmutableList.of(CLOSURE_BASE, src), 0, 0, otherSrc, false); try { doAddScript(compiler, updatedOtherSrc, 1); fail("Expected an IllegalStateException to be thrown"); } catch (IllegalStateException expectedISE) { //ignore expected exception } // Position of the definition will not have moved as we did not add the // updated script verifyPropertyLineno(compiler, "Bar", "foo", 2); } }