/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import static com.google.javascript.jscomp.ProcessClosurePrimitives.BASE_CLASS_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.CLASS_NAMESPACE_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.CLOSURE_DEFINES_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.DUPLICATE_NAMESPACE_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.EXPECTED_OBJECTLIT_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.FUNCTION_NAMESPACE_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.GOOG_BASE_CLASS_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.INVALID_ARGUMENT_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.INVALID_CLOSURE_CALL_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.INVALID_CSS_RENAMING_MAP; import static com.google.javascript.jscomp.ProcessClosurePrimitives.INVALID_DEFINE_NAME_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.INVALID_PROVIDE_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.INVALID_STYLE_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.LATE_PROVIDE_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.MISSING_DEFINE_ANNOTATION; import static com.google.javascript.jscomp.ProcessClosurePrimitives.MISSING_PROVIDE_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.NULL_ARGUMENT_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.TOO_MANY_ARGUMENTS_ERROR; import static com.google.javascript.jscomp.ProcessClosurePrimitives.WEAK_NAMESPACE_TYPE; import static com.google.javascript.jscomp.ProcessClosurePrimitives.XMODULE_REQUIRE_ERROR; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.rhino.Node; /** * Tests for {@link ProcessClosurePrimitives}. * */ public final class ProcessClosurePrimitivesTest extends Es6CompilerTestCase { private String additionalCode; private String additionalEndCode; private boolean addAdditionalNamespace; private boolean preserveGoogProvidesAndRequires; private boolean banGoogBase; @Override protected void setUp() { additionalCode = null; additionalEndCode = null; addAdditionalNamespace = false; preserveGoogProvidesAndRequires = false; banGoogBase = false; } @Override protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); if (banGoogBase) { options.setWarningLevel( DiagnosticGroups.USE_OF_GOOG_BASE, CheckLevel.ERROR); } return options; } @Override protected CompilerPass getProcessor(final Compiler compiler) { if ((additionalCode == null) && (additionalEndCode == null)) { return new ProcessClosurePrimitives( compiler, null, CheckLevel.ERROR, preserveGoogProvidesAndRequires); } else { return new CompilerPass() { @Override public void process(Node externs, Node root) { // Process the original code. new ProcessClosurePrimitives( compiler, null, CheckLevel.OFF, preserveGoogProvidesAndRequires) .process(externs, root); // Inject additional code at the beginning. if (additionalCode != null) { SourceFile file = SourceFile.fromCode("additionalcode", additionalCode); Node scriptNode = root.getFirstChild(); Node newScriptNode = new CompilerInput(file).getAstRoot(compiler); if (addAdditionalNamespace) { newScriptNode.getFirstChild() .putBooleanProp(Node.IS_NAMESPACE, true); } while (newScriptNode.getLastChild() != null) { Node lastChild = newScriptNode.getLastChild(); newScriptNode.removeChild(lastChild); scriptNode.addChildBefore(lastChild, scriptNode.getFirstChild()); } } // Inject additional code at the end. if (additionalEndCode != null) { SourceFile file = SourceFile.fromCode("additionalendcode", additionalEndCode); Node scriptNode = root.getFirstChild(); Node newScriptNode = new CompilerInput(file).getAstRoot(compiler); if (addAdditionalNamespace) { newScriptNode.getFirstChild() .putBooleanProp(Node.IS_NAMESPACE, true); } while (newScriptNode.getFirstChild() != null) { Node firstChild = newScriptNode.getFirstChild(); newScriptNode.removeChild(firstChild); scriptNode.addChildToBack(firstChild); } } // Process the tree a second time. new ProcessClosurePrimitives( compiler, null, CheckLevel.ERROR, preserveGoogProvidesAndRequires) .process(externs, root); } }; } } @Override protected int getNumRepetitions() { return 1; } public void testSimpleProvides() { test("goog.provide('foo');", "/** @const */ var foo={};"); test("goog.provide('foo.bar');", "/** @const */ var foo={}; /** @const */ foo.bar={};"); test( "goog.provide('foo.bar.baz');", "/** @const */ var foo={}; /** @const */ foo.bar={}; /** @const */ foo.bar.baz={};"); test( "goog.provide('foo.bar.baz.boo');", LINE_JOINER.join( "/** @const */ var foo={};", "/** @const */ foo.bar={};", "/** @const */ foo.bar.baz={};", "/** @const */ foo.bar.baz.boo={};")); test("goog.provide('goog.bar');", "/** @const */ goog.bar={};"); // goog is special-cased } public void testMultipleProvides() { test( "goog.provide('foo.bar'); goog.provide('foo.baz');", "/** @const */ var foo={}; /** @const */ foo.bar={}; /** @const */ foo.baz={};"); test( "goog.provide('foo.bar.baz'); goog.provide('foo.boo.foo');", LINE_JOINER.join( "/** @const */", "var foo = {};", "/** @const */", "foo.bar={};", "/** @const */", "foo.bar.baz={};", "/** @const */", "foo.boo={};", "/** @const */", "foo.boo.foo={};")); test( "goog.provide('foo.bar.baz'); goog.provide('foo.bar.boo');", LINE_JOINER.join( "/** @const */", "var foo = {};", "/** @const */", "foo.bar = {};", "/** @const */", "foo.bar.baz = {};", "/** @const */", "foo.bar.boo={};")); test( "goog.provide('foo.bar.baz'); goog.provide('goog.bar.boo');", LINE_JOINER.join( "/** @const */", "var foo = {};", "/** @const */", "foo.bar={};", "/** @const */", "foo.bar.baz={};", "/** @const */", "goog.bar={};", "/** @const */", "goog.bar.boo = {};")); } public void testRemovalOfProvidedObjLit() { test("goog.provide('foo'); foo = 0;", "var foo = 0;"); test("goog.provide('foo'); foo = {a: 0};", "var foo = {a: 0};"); test("goog.provide('foo'); foo = function(){};", "var foo = function(){};"); testEs6("goog.provide('foo'); foo = ()=>{};", "var foo = ()=>{};"); test("goog.provide('foo'); var foo = 0;", "var foo = 0;"); testEs6("goog.provide('foo'); let foo = 0;", "let foo = 0;"); testEs6("goog.provide('foo'); const foo = 0;", "const foo = 0;"); test("goog.provide('foo'); var foo = {a: 0};", "var foo = {a: 0};"); test("goog.provide('foo'); var foo = function(){};", "var foo = function(){};"); testEs6("goog.provide('foo'); var foo = ()=>{};", "var foo = ()=>{};"); test( "goog.provide('foo.bar.Baz'); foo.bar.Baz=function(){};", LINE_JOINER.join( "/** @const */", "var foo={};", "/** @const */", "foo.bar = {};", "foo.bar.Baz=function(){};")); test( "goog.provide('foo.bar.moo'); foo.bar.moo={E:1,S:2};", LINE_JOINER.join( "/** @const */", "var foo={};", "/** @const */", "foo.bar={};", "foo.bar.moo={E:1,S:2};")); test( "goog.provide('foo.bar.moo'); foo.bar.moo={E:1}; foo.bar.moo={E:2};", LINE_JOINER.join( "/** @const */", "var foo={};", "/** @const */", "foo.bar={};", "foo.bar.moo={E:1};", "foo.bar.moo={E:2};")); testEs6("goog.provide('foo'); var foo = class {}", "var foo = class {}"); } public void testProvidedDeclaredFunctionError() { testError("goog.provide('foo'); function foo(){}", FUNCTION_NAMESPACE_ERROR); } public void testProvidedDeclaredClassError() { testErrorEs6("goog.provide('foo'); class foo {}", CLASS_NAMESPACE_ERROR); } public void testRemovalMultipleAssignment1() { test("goog.provide('foo'); foo = 0; foo = 1", "var foo = 0; foo = 1;"); } public void testRemovalMultipleAssignment2() { test("goog.provide('foo'); var foo = 0; foo = 1", "var foo = 0; foo = 1;"); testEs6("goog.provide('foo'); let foo = 0; let foo = 1", "let foo = 0; let foo = 1;"); } public void testRemovalMultipleAssignment3() { test("goog.provide('foo'); foo = 0; var foo = 1", "foo = 0; var foo = 1;"); testEs6("goog.provide('foo'); foo = 0; let foo = 1", "foo = 0; let foo = 1;"); } public void testRemovalMultipleAssignment4() { test( "goog.provide('foo.bar'); foo.bar = 0; foo.bar = 1", "/** @const */ var foo = {}; foo.bar = 0; foo.bar = 1"); } public void testNoRemovalFunction1() { test( "goog.provide('foo'); function f(){foo = 0}", "/** @const */ var foo = {}; function f(){foo = 0}"); } public void testNoRemovalFunction2() { test( "goog.provide('foo'); function f(){var foo = 0}", "/** @const */ var foo = {}; function f(){var foo = 0}"); } public void testNoRemovalFunction3() { testEs6( "goog.provide('foo'); function f(foo = 0){}", "/** @const */ var foo = {}; function f(foo = 0){}"); } public void testRemovalMultipleAssignmentInIf1() { test("goog.provide('foo'); if (true) { var foo = 0 } else { foo = 1 }", "if (true) { var foo = 0 } else { foo = 1 }"); } public void testRemovalMultipleAssignmentInIf2() { test("goog.provide('foo'); if (true) { foo = 0 } else { var foo = 1 }", "if (true) { foo = 0 } else { var foo = 1 }"); } public void testRemovalMultipleAssignmentInIf3() { test("goog.provide('foo'); if (true) { foo = 0 } else { foo = 1 }", "if (true) { var foo = 0 } else { foo = 1 }"); } public void testRemovalMultipleAssignmentInIf4() { test( "goog.provide('foo.bar'); if (true) { foo.bar = 0 } else { foo.bar = 1 }", LINE_JOINER.join( "/** @const */ var foo = {};", "if (true) {", " foo.bar = 0;", "} else {", " foo.bar = 1;", "}")); } public void testMultipleDeclarationError1() { String rest = "if (true) { foo.bar = 0 } else { foo.bar = 1 }"; test("goog.provide('foo.bar');" + "var foo = {};" + rest, "var foo = {};" + "var foo = {};" + rest); } public void testMultipleDeclarationError2() { test( LINE_JOINER.join( "goog.provide('foo.bar');", "if (true) { var foo = {}; foo.bar = 0 } else { foo.bar = 1 }"), LINE_JOINER.join( "var foo = {};", "if (true) {", " var foo = {}; foo.bar = 0", "} else {", " foo.bar = 1", "}")); } public void testMultipleDeclarationError3() { test( LINE_JOINER.join( "goog.provide('foo.bar');", "if (true) { foo.bar = 0 } else { var foo = {}; foo.bar = 1 }"), LINE_JOINER.join( "var foo = {};", "if (true) {", " foo.bar = 0", "} else {", " var foo = {}; foo.bar = 1", "}")); } public void testProvideAfterDeclarationError() { test("var x = 42; goog.provide('x');", "var x = 42; /** @const */ var x = {}"); } public void testProvideErrorCases() { testError("goog.provide();", NULL_ARGUMENT_ERROR); testError("goog.provide(5);", INVALID_ARGUMENT_ERROR); testError("goog.provide([]);", INVALID_ARGUMENT_ERROR); testError("goog.provide({});", INVALID_ARGUMENT_ERROR); testError("goog.provide('foo', 'bar');", TOO_MANY_ARGUMENTS_ERROR); testError("goog.provide('foo'); goog.provide('foo');", DUPLICATE_NAMESPACE_ERROR); testError("goog.provide('foo.bar'); goog.provide('foo'); goog.provide('foo');", DUPLICATE_NAMESPACE_ERROR); testErrorEs6("goog.provide(`template`);", INVALID_ARGUMENT_ERROR); testErrorEs6("goog.provide(tagged`template`);", INVALID_ARGUMENT_ERROR); testErrorEs6("goog.provide(`${template}Sub`);", INVALID_ARGUMENT_ERROR); } public void testProvideErrorCases2() { test("goog.provide('foo'); /** @type {Object} */ var foo = {};", "/** @type {Object} */ var foo={};", null, WEAK_NAMESPACE_TYPE); test("goog.provide('foo'); /** @type {!Object} */ var foo = {};", "/** @type {!Object} */ var foo={};", null, WEAK_NAMESPACE_TYPE); test("goog.provide('foo.bar'); /** @type {Object} */ foo.bar = {};", "/** @const */ var foo = {}; /** @type {Object} */ foo.bar = {};", null, WEAK_NAMESPACE_TYPE); test("goog.provide('foo.bar'); /** @type {!Object} */ foo.bar = {};", "/** @const */ var foo={}; /** @type {!Object} */ foo.bar={};", null, WEAK_NAMESPACE_TYPE); test("goog.provide('foo'); /** @type {Object<string>} */ var foo = {};", "/** @type {Object<string>} */ var foo={};"); } public void testProvideValidObjectType() { test( "goog.provide('foo'); /** @type {Object<string>} */ var foo = {};", "/** @type {Object<string>} */ var foo = {};"); } public void testRemovalOfRequires() { test("goog.provide('foo'); goog.require('foo');", "/** @const */ var foo={};"); test( "goog.provide('foo.bar'); goog.require('foo.bar');", "/** @const */ var foo={}; /** @const */ foo.bar={};"); test("goog.provide('foo.bar.baz'); goog.require('foo.bar.baz');", "/** @const */ var foo={}; /** @const */ foo.bar={}; /** @const */ foo.bar.baz={};"); test("goog.provide('foo'); var x = 3; goog.require('foo'); something();", "/** @const */ var foo={}; var x = 3; something();"); testSame("foo.require('foo.bar');"); } public void testPreserveGoogRequires() { preserveGoogProvidesAndRequires = true; test( "goog.provide('foo'); goog.require('foo');", "/** @const */ var foo={}; goog.provide('foo'); goog.require('foo');"); test( "goog.provide('foo'); goog.require('foo'); var a = {};", "/** @const */ var foo = {}; goog.provide('foo'); goog.require('foo'); var a = {};"); } public void testRequireErrorCases() { testError("goog.require();", NULL_ARGUMENT_ERROR); testError("goog.require(5);", INVALID_ARGUMENT_ERROR); testError("goog.require([]);", INVALID_ARGUMENT_ERROR); testError("goog.require({});", INVALID_ARGUMENT_ERROR); testErrorEs6("goog.require(`template`);", INVALID_ARGUMENT_ERROR); testErrorEs6("goog.require(tagged`template`);", INVALID_ARGUMENT_ERROR); testErrorEs6("goog.require(`${template}Sub`);", INVALID_ARGUMENT_ERROR); } public void testLateProvides() { testError("goog.require('foo'); goog.provide('foo');", LATE_PROVIDE_ERROR); testError("goog.require('foo.bar'); goog.provide('foo.bar');", LATE_PROVIDE_ERROR); testError("goog.provide('foo.bar'); goog.require('foo'); goog.provide('foo');", LATE_PROVIDE_ERROR); } public void testMissingProvides() { testError("goog.require('foo');", MISSING_PROVIDE_ERROR); testError("goog.provide('foo'); goog.require('Foo');", MISSING_PROVIDE_ERROR); testError("goog.provide('foo'); goog.require('foo.bar');", MISSING_PROVIDE_ERROR); testError("goog.provide('foo'); var EXPERIMENT_FOO = true; " + "if (EXPERIMENT_FOO) {goog.require('foo.bar');}", MISSING_PROVIDE_ERROR); } public void testProvideInExterns() { allowExternsChanges(true); test( "/** @externs */ goog.provide('animals.Dog');" + "/** @constructor */ animals.Dog = function() {}", "goog.require('animals.Dog'); new animals.Dog()", "new animals.Dog();", null, null); } public void testAddDependency() { test("goog.addDependency('x.js', ['A', 'B'], []);", "0"); Compiler compiler = getLastCompiler(); assertTrue(compiler.getTypeRegistry().isForwardDeclaredType("A")); assertTrue(compiler.getTypeRegistry().isForwardDeclaredType("B")); assertFalse(compiler.getTypeRegistry().isForwardDeclaredType("C")); } public void testForwardDeclarations() { test("goog.forwardDeclare('A.B')", ""); Compiler compiler = getLastCompiler(); assertTrue(compiler.getTypeRegistry().isForwardDeclaredType("A.B")); assertFalse(compiler.getTypeRegistry().isForwardDeclaredType("C.D")); testError("goog.forwardDeclare();", ProcessClosurePrimitives.INVALID_FORWARD_DECLARE); testError("goog.forwardDeclare('A.B', 'C.D');", ProcessClosurePrimitives.INVALID_FORWARD_DECLARE); testErrorEs6("goog.forwardDeclare(`template`);", ProcessClosurePrimitives.INVALID_FORWARD_DECLARE); testErrorEs6("goog.forwardDeclare(`${template}Sub`);", ProcessClosurePrimitives.INVALID_FORWARD_DECLARE); } public void testValidSetCssNameMapping() { test("goog.setCssNameMapping({foo:'bar',\"biz\":'baz'});", ""); CssRenamingMap map = getLastCompiler().getCssRenamingMap(); assertNotNull(map); assertEquals("bar", map.get("foo")); assertEquals("baz", map.get("biz")); } public void testValidSetCssNameMappingWithType() { test("goog.setCssNameMapping({foo:'bar',\"biz\":'baz'}, 'BY_PART');", ""); CssRenamingMap map = getLastCompiler().getCssRenamingMap(); assertNotNull(map); assertEquals("bar", map.get("foo")); assertEquals("baz", map.get("biz")); test("goog.setCssNameMapping({foo:'bar',biz:'baz','biz-foo':'baz-bar'}," + " 'BY_WHOLE');", ""); map = getLastCompiler().getCssRenamingMap(); assertNotNull(map); assertEquals("bar", map.get("foo")); assertEquals("baz", map.get("biz")); assertEquals("baz-bar", map.get("biz-foo")); } public void testSetCssNameMappingByShortHand() { testErrorEs6("goog.setCssNameMapping({shortHandFirst, shortHandSecond});", NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR); } public void testSetCssNameMappingByTemplate() { testErrorEs6("goog.setCssNameMapping({foo: `bar`});", NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR); testErrorEs6("goog.setCssNameMapping({foo: `${vari}bar`});", NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR); } public void testSetCssNameMappingNonStringValueReturnsError() { // Make sure the argument is an object literal. testError("var BAR = {foo:'bar'}; goog.setCssNameMapping(BAR);", EXPECTED_OBJECTLIT_ERROR); testError("goog.setCssNameMapping([]);", EXPECTED_OBJECTLIT_ERROR); testError("goog.setCssNameMapping(false);", EXPECTED_OBJECTLIT_ERROR); testError("goog.setCssNameMapping(null);", EXPECTED_OBJECTLIT_ERROR); testError("goog.setCssNameMapping(undefined);", EXPECTED_OBJECTLIT_ERROR); // Make sure all values of the object literal are string literals. testError("var BAR = 'bar'; goog.setCssNameMapping({foo:BAR});", NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR); testError("goog.setCssNameMapping({foo:6});", NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR); testError("goog.setCssNameMapping({foo:false});", NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR); testError("goog.setCssNameMapping({foo:null});", NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR); testError("goog.setCssNameMapping({foo:undefined});", NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR); } public void testSetCssNameMappingValidity() { // Make sure that the keys don't have -'s test("goog.setCssNameMapping({'a': 'b', 'a-a': 'c'})", "", null, INVALID_CSS_RENAMING_MAP); // In full mode, we check that map(a-b)=map(a)-map(b) test("goog.setCssNameMapping({'a': 'b', 'a-a': 'c'}, 'BY_WHOLE')", "", null, INVALID_CSS_RENAMING_MAP); // Unknown mapping type testError("goog.setCssNameMapping({foo:'bar'}, 'UNKNOWN');", INVALID_STYLE_ERROR); } public void testBadCrossModuleRequire() { test( createModuleStar("", "goog.provide('goog.ui');", "goog.require('goog.ui');"), new String[] {"", "/** @const */ goog.ui = {};", ""}, null, XMODULE_REQUIRE_ERROR); } public void testGoodCrossModuleRequire1() { test( createModuleStar("goog.provide('goog.ui');", "", "goog.require('goog.ui');"), new String[] { "/** @const */ goog.ui = {};", "", "", }); } public void testGoodCrossModuleRequire2() { test( createModuleStar("", "", "goog.provide('goog.ui'); goog.require('goog.ui');"), new String[] { "", "", "/** @const */ goog.ui = {};", }); } // Tests providing additional code with non-overlapping var namespace. public void testSimpleAdditionalProvide() { additionalCode = "goog.provide('b.B'); b.B = {};"; test("goog.provide('a.A'); a.A = {};", "/** @const */ var b={};b.B={}; /** @const */ var a={};a.A={};"); } // Same as above, but with the additional code added after the original. public void testSimpleAdditionalProvideAtEnd() { additionalEndCode = "goog.provide('b.B'); b.B = {};"; test("goog.provide('a.A'); a.A = {};", "/** @const */ var a={}; a.A={}; /** @const */ var b={};b.B={};"); } // Tests providing additional code with non-overlapping dotted namespace. public void testSimpleDottedAdditionalProvide() { additionalCode = "goog.provide('a.b.B'); a.b.B = {};"; test("goog.provide('c.d.D'); c.d.D = {};", LINE_JOINER.join( "/** @const */", "var a={};", "/** @const */", "a.b={};", "a.b.B={};", "/** @const */", "var c={};", "/** @const */", "c.d={};", "c.d.D={};")); } // Tests providing additional code with overlapping var namespace. public void testOverlappingAdditionalProvide() { additionalCode = "goog.provide('a.B'); a.B = {};"; test("goog.provide('a.A'); a.A = {};", "/** @const */ var a={};a.B={};a.A={};"); } // Tests providing additional code with overlapping var namespace. public void testOverlappingAdditionalProvideAtEnd() { additionalEndCode = "goog.provide('a.B'); a.B = {};"; test("goog.provide('a.A'); a.A = {};", "/** @const */ var a={};a.A={};a.B={};"); } // Tests providing additional code with overlapping dotted namespace. public void testOverlappingDottedAdditionalProvide() { additionalCode = "goog.provide('a.b.B'); a.b.B = {};"; test( "goog.provide('a.b.C'); a.b.C = {};", LINE_JOINER.join( "/** @const */", "var a={};", "/** @const */ a.b={};", "a.b.B={};", "a.b.C={};")); } // Tests that a require of additional code generates no error. public void testRequireOfAdditionalProvide() { additionalCode = "goog.provide('b.B'); b.B = {};"; test("goog.require('b.B'); goog.provide('a.A'); a.A = {};", "/** @const */ var b={};b.B={}; /** @const */ var a={};a.A={};"); } // Tests that a require not in additional code generates (only) one error. public void testMissingRequireWithAdditionalProvide() { additionalCode = "goog.provide('b.B'); b.B = {};"; testError("goog.require('b.C'); goog.provide('a.A'); a.A = {};", MISSING_PROVIDE_ERROR); } // Tests that a require in additional code generates no error. public void testLateRequire() { additionalEndCode = "goog.require('a.A');"; test("goog.provide('a.A'); a.A = {};", "/** @const */ var a={}; a.A={};"); } // Tests a case where code is reordered after processing provides and then // provides are processed again. public void testReorderedProvides() { additionalCode = "a.B = {};"; // as if a.B was after a.A originally addAdditionalNamespace = true; test( "goog.provide('a.A'); a.A = {};", LINE_JOINER.join( "/** @const */", "var a = {};", "a.B = {};", "a.A = {};")); } // Another version of above. public void testReorderedProvides2() { additionalEndCode = "a.B = {};"; addAdditionalNamespace = true; test("goog.provide('a.A'); a.A = {};", "/** @const */ var a={};a.A={};a.B={};"); } // Provide a name before the definition of the class providing the // parent namespace. public void testProvideOrder1() { additionalEndCode = ""; addAdditionalNamespace = false; // TODO(johnlenz): This test confirms that the constructor (a.b) isn't // improperly removed, but this result isn't really what we want as the // reassign of a.b removes the definition of "a.b.c". test( LINE_JOINER.join( "goog.provide('a.b');", "goog.provide('a.b.c');", "a.b.c;", "a.b = function(x,y) {};"), LINE_JOINER.join( "/** @const */", "var a = {};", "/** @const */", "a.b = {};", "/** @const */", "a.b.c = {};", "a.b.c;", "a.b = function(x,y) {};")); } // Provide a name after the definition of the class providing the // parent namespace. public void testProvideOrder2() { additionalEndCode = ""; addAdditionalNamespace = false; // TODO(johnlenz): This test confirms that the constructor (a.b) isn't // improperly removed, but this result isn't really what we want as // namespace placeholders for a.b and a.b.c remain. test( LINE_JOINER.join( "goog.provide('a.b');", "goog.provide('a.b.c');", "a.b = function(x,y) {};", "a.b.c;"), LINE_JOINER.join( "/** @const */", "var a = {};", "/** @const */", "a.b = {};", "/** @const */", "a.b.c = {};", "a.b = function(x,y) {};", "a.b.c;")); } // Provide a name after the definition of the class providing the // parent namespace. public void testProvideOrder3a() { test( LINE_JOINER.join( "goog.provide('a.b');", "a.b = function(x,y) {};", "goog.provide('a.b.c');", "a.b.c;"), LINE_JOINER.join( "/** @const */", "var a = {};", "a.b = function(x,y) {};", "/** @const */", "a.b.c = {};", "a.b.c;")); } public void testProvideOrder3b() { additionalEndCode = ""; addAdditionalNamespace = false; // This tests a cleanly provided name, below a function namespace. test( LINE_JOINER.join( "goog.provide('a.b');", "a.b = function(x,y) {};", "goog.provide('a.b.c');", "a.b.c;"), LINE_JOINER.join( "/** @const */", "var a = {};", "a.b = function(x,y) {};", "/** @const */", "a.b.c = {};", "a.b.c;")); } public void testProvideOrder4a() { test( LINE_JOINER.join( "goog.provide('goog.a');", "goog.provide('goog.a.b');", "if (x) {", " goog.a.b = 1;", "} else {", " goog.a.b = 2;", "}"), LINE_JOINER.join( "/** @const */", "goog.a={};", "if(x)", " goog.a.b=1;", "else", " goog.a.b=2;")); } public void testProvideOrder4b() { additionalEndCode = ""; addAdditionalNamespace = false; // This tests a cleanly provided name, below a namespace. test( LINE_JOINER.join( "goog.provide('goog.a');", "goog.provide('goog.a.b');", "if (x) {", " goog.a.b = 1;", "} else {", " goog.a.b = 2;", "}"), LINE_JOINER.join( "/** @const */", "goog.a={};", "if(x)", " goog.a.b=1;", "else", " goog.a.b=2;")); } public void testInvalidProvide() { test("goog.provide('a.class');", "/** @const */ var a = {}; /** @const */ a.class = {};"); testError("goog.provide('class.a');", INVALID_PROVIDE_ERROR); testError("goog.provide('a.class');", INVALID_PROVIDE_ERROR, LanguageMode.ECMASCRIPT3); testError("goog.provide('class.a');", INVALID_PROVIDE_ERROR, LanguageMode.ECMASCRIPT3); } public void testInvalidRequire() { test( "goog.provide('a.b'); goog.require('a.b');", "/** @const */ var a = {}; /** @const */ a.b = {};"); testError("goog.provide('a.b'); var x = x || goog.require('a.b');", INVALID_CLOSURE_CALL_ERROR); testError("goog.provide('a.b'); x = goog.require('a.b');", INVALID_CLOSURE_CALL_ERROR); testError("goog.provide('a.b'); function f() { goog.require('a.b'); }", INVALID_CLOSURE_CALL_ERROR); } public void testValidGoogMethod() { testSame("function f() { goog.isDef('a.b'); }"); testSame("function f() { goog.inherits(a, b); }"); testSame("function f() { goog.exportSymbol(a, b); }"); test("function f() { goog.setCssNameMapping({}); }", "function f() {}"); testSame("x || goog.isDef('a.b');"); testSame("x || goog.inherits(a, b);"); testSame("x || goog.exportSymbol(a, b);"); testSame("x || void 0"); } private static final String METHOD_FORMAT = "function Foo() {} Foo.prototype.method = function() { %s };"; private static final String FOO_INHERITS = "goog.inherits(Foo, BaseFoo);"; public void testInvalidGoogBase1() { testError("goog.base(this, 'method');", GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase2() { testError("function Foo() {}" + "Foo.method = function() {" + " goog.base(this, 'method');" + "};", GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase3() { testError(String.format(METHOD_FORMAT, "goog.base();"), GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase4() { testError(String.format(METHOD_FORMAT, "goog.base(this, 'bar');"), GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase5() { testError(String.format(METHOD_FORMAT, "goog.base('foo', 'method');"), GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase6() { testError(String.format(METHOD_FORMAT, "goog.base.call(null, this, 'method');"), GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase6b() { testError(String.format(METHOD_FORMAT, "goog.base.call(this, 'method');"), GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase7() { testError("function Foo() { goog.base(this); }", GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase8() { testError("var Foo = function() { goog.base(this); }", GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase9() { testError("var goog = {}; goog.Foo = function() { goog.base(this); }", GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase10() { testErrorEs6("class Foo extends BaseFoo { constructor() { goog.base(this); } }", GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase11() { testErrorEs6("class Foo extends BaseFoo { someMethod() { goog.base(this, 'someMethod'); } }", GOOG_BASE_CLASS_ERROR); } public void testValidGoogBase1() { test(String.format(METHOD_FORMAT, "goog.base(this, 'method');"), String.format(METHOD_FORMAT, "Foo.superClass_.method.call(this)")); } public void testValidGoogBase2() { test(String.format(METHOD_FORMAT, "goog.base(this, 'method', 1, 2);"), String.format(METHOD_FORMAT, "Foo.superClass_.method.call(this, 1, 2)")); } public void testValidGoogBase3() { test(String.format(METHOD_FORMAT, "return goog.base(this, 'method');"), String.format(METHOD_FORMAT, "return Foo.superClass_.method.call(this)")); } public void testValidGoogBase4() { test("function Foo() { goog.base(this, 1, 2); }" + FOO_INHERITS, "function Foo() { BaseFoo.call(this, 1, 2); } " + FOO_INHERITS); } public void testValidGoogBase5() { test("var Foo = function() { goog.base(this, 1); };" + FOO_INHERITS, "var Foo = function() { BaseFoo.call(this, 1); }; " + FOO_INHERITS); } public void testValidGoogBase6() { test("var goog = {}; goog.Foo = function() { goog.base(this); }; " + "goog.inherits(goog.Foo, goog.BaseFoo);", "var goog = {}; goog.Foo = function() { goog.BaseFoo.call(this); }; " + "goog.inherits(goog.Foo, goog.BaseFoo);"); } public void testBanGoogBase() { banGoogBase = true; testError( "function Foo() { goog.base(this, 1, 2); }" + FOO_INHERITS, ProcessClosurePrimitives.USE_OF_GOOG_BASE); } public void testInvalidBase1() { testError( "var Foo = function() {};" + FOO_INHERITS + "Foo.base(this, 'method');", BASE_CLASS_ERROR); } public void testInvalidBase2() { testError("function Foo() {}" + FOO_INHERITS + "Foo.method = function() {" + " Foo.base(this, 'method');" + "};", BASE_CLASS_ERROR); } public void testInvalidBase3() { testError(String.format(FOO_INHERITS + METHOD_FORMAT, "Foo.base();"), BASE_CLASS_ERROR); } public void testInvalidBase4() { testError(String.format(FOO_INHERITS + METHOD_FORMAT, "Foo.base(this, 'bar');"), BASE_CLASS_ERROR); } public void testInvalidBase5() { testError(String.format(FOO_INHERITS + METHOD_FORMAT, "Foo.base('foo', 'method');"), BASE_CLASS_ERROR); } public void testInvalidBase7() { testError("function Foo() { Foo.base(this); };" + FOO_INHERITS, BASE_CLASS_ERROR); } public void testInvalidBase8() { testError("var Foo = function() { Foo.base(this); };" + FOO_INHERITS, BASE_CLASS_ERROR); } public void testInvalidBase9() { testError("var goog = {}; goog.Foo = function() { goog.Foo.base(this); };" + FOO_INHERITS, BASE_CLASS_ERROR); } public void testInvalidBase10() { testError("function Foo() { Foo.base(this); }" + FOO_INHERITS, BASE_CLASS_ERROR); } public void testInvalidBase11() { testError("function Foo() { Foo.base(this, 'method'); }" + FOO_INHERITS, BASE_CLASS_ERROR); } public void testInvalidBase12() { testError("function Foo() { Foo.base(this, 1, 2); }" + FOO_INHERITS, BASE_CLASS_ERROR); } public void testInvalidBase13() { testError( "function Bar(){ Bar.base(this, 'constructor'); }" + "goog.inherits(Bar, Goo);" + "function Foo(){ Bar.base(this, 'constructor'); }" + FOO_INHERITS, BASE_CLASS_ERROR); } public void testInvalidGoogBase14() { testErrorEs6("class Foo extends BaseFoo { constructor() { Foo.base(this); } }", GOOG_BASE_CLASS_ERROR); } public void testInvalidGoogBase14b() { testErrorEs6("class Foo extends BaseFoo { method() { Foo.base(this, 'method'); } }", GOOG_BASE_CLASS_ERROR); } public void testValidBase1() { test(FOO_INHERITS + String.format(METHOD_FORMAT, "Foo.base(this, 'method');"), FOO_INHERITS + String.format(METHOD_FORMAT, "Foo.superClass_.method.call(this)")); } public void testValidBase2() { test(FOO_INHERITS + String.format(METHOD_FORMAT, "Foo.base(this, 'method', 1, 2);"), FOO_INHERITS + String.format(METHOD_FORMAT, "Foo.superClass_.method.call(this, 1, 2)")); } public void testValidBase3() { test(FOO_INHERITS + String.format(METHOD_FORMAT, "return Foo.base(this, 'method');"), FOO_INHERITS + String.format(METHOD_FORMAT, "return Foo.superClass_.method.call(this)")); } public void testValidBase4() { test("function Foo() { Foo.base(this, 'constructor', 1, 2); }" + FOO_INHERITS, "function Foo() { BaseFoo.call(this, 1, 2); } " + FOO_INHERITS); } public void testValidBase5() { test("var Foo = function() { Foo.base(this, 'constructor', 1); };" + FOO_INHERITS, "var Foo = function() { BaseFoo.call(this, 1); }; " + FOO_INHERITS); } public void testValidBase6() { test("var goog = {}; goog.Foo = function() {" + "goog.Foo.base(this, 'constructor'); }; " + "goog.inherits(goog.Foo, goog.BaseFoo);", "var goog = {}; goog.Foo = function() { goog.BaseFoo.call(this); }; " + "goog.inherits(goog.Foo, goog.BaseFoo);"); } public void testValidBase7() { // No goog.inherits, so this is probably a different 'base' function. testSame("" + "var a = function() {" + " a.base(this, 'constructor');" + "};"); } public void testImplicitAndExplicitProvide() { test( "var goog = {}; goog.provide('goog.foo.bar'); goog.provide('goog.foo');", LINE_JOINER.join( "var goog = {};", "/** @const */", "goog.foo = {};", "/** @const */", "goog.foo.bar = {};")); } public void testImplicitProvideInIndependentModules() { testModule( new String[] {"", "goog.provide('apps.A');", "goog.provide('apps.B');"}, new String[] { "/** @const */ var apps = {};", "/** @const */ apps.A = {};", "/** @const */ apps.B = {};", }); } public void testImplicitProvideInIndependentModules2() { testModule( new String[] { "goog.provide('apps');", "goog.provide('apps.foo.A');", "goog.provide('apps.foo.B');" }, new String[] { "/** @const */ var apps = {}; /** @const */ apps.foo = {};", "/** @const */ apps.foo.A = {};", "/** @const */ apps.foo.B = {};", }); } public void testImplicitProvideInIndependentModules3() { testModule( new String[] { "var goog = {};", "goog.provide('goog.foo.A');", "goog.provide('goog.foo.B');" }, new String[] { "var goog = {}; /** @const */ goog.foo = {};", "/** @const */ goog.foo.A = {};", "/** @const */ goog.foo.B = {};", }); } public void testProvideInIndependentModules1() { testModule( new String[] { "goog.provide('apps');", "goog.provide('apps.foo');", "goog.provide('apps.foo.B');" }, new String[] { "/** @const */ var apps = {}; /** @const */ apps.foo = {};", "", "/** @const */ apps.foo.B = {};", }); } public void testProvideInIndependentModules2() { // TODO(nicksantos): Make this an error. testModule( new String[] { "goog.provide('apps');", "goog.provide('apps.foo'); apps.foo = {};", "goog.provide('apps.foo.B');" }, new String[] { "/** @const */ var apps = {};", "apps.foo = {};", "/** @const */ apps.foo.B = {};", }); } public void testProvideInIndependentModules2b() { // TODO(nicksantos): Make this an error. testModule( new String[] { "goog.provide('apps');", "goog.provide('apps.foo'); apps.foo = function() {};", "goog.provide('apps.foo.B');" }, new String[] { "/** @const */ var apps = {};", "apps.foo = function() {};", "/** @const */ apps.foo.B = {};", }); } public void testProvideInIndependentModules3() { testModule( new String[] { "goog.provide('apps');", "goog.provide('apps.foo.B');", "goog.provide('apps.foo'); goog.require('apps.foo');" }, new String[] { "/** @const */ var apps = {}; /** @const */ apps.foo = {};", "/** @const */ apps.foo.B = {};", "", }); } public void testProvideInIndependentModules3b() { // TODO(nicksantos): Make this an error. testModule( new String[] { "goog.provide('apps');", "goog.provide('apps.foo.B');", "goog.provide('apps.foo'); apps.foo = function() {}; " + "goog.require('apps.foo');" }, new String[] { "/** @const */ var apps = {};", "/** @const */ apps.foo.B = {};", "apps.foo = function() {};", }); } public void testProvideInIndependentModules4() { // Regression test for bug 261: // http://blickly.github.io/closure-compiler-issues/#261 testModule( new String[] { "goog.provide('apps');", "goog.provide('apps.foo.bar.B');", "goog.provide('apps.foo.bar.C');" }, new String[] { "/** @const */ var apps={}; /** @const */ apps.foo={}; /** @const */ apps.foo.bar={}", "/** @const */ apps.foo.bar.B = {};", "/** @const */ apps.foo.bar.C = {};", }); } public void testRequireOfBaseGoog() { testError("goog.require('goog');", MISSING_PROVIDE_ERROR); } public void testSourcePositionPreservation() { test("goog.provide('foo.bar.baz');", "/** @const */ var foo = {};" + "/** @const */ foo.bar = {};" + "/** @const */ foo.bar.baz = {};"); Node root = getLastCompiler().getRoot(); Node fooDecl = findQualifiedNameNode("foo", root); Node fooBarDecl = findQualifiedNameNode("foo.bar", root); Node fooBarBazDecl = findQualifiedNameNode("foo.bar.baz", root); assertEquals(1, fooDecl.getLineno()); assertEquals(14, fooDecl.getCharno()); assertEquals(1, fooBarDecl.getLineno()); assertEquals(18, fooBarDecl.getCharno()); assertEquals(1, fooBarBazDecl.getLineno()); assertEquals(22, fooBarBazDecl.getCharno()); } public void testNoStubForProvidedTypedef() { test("goog.provide('x'); /** @typedef {number} */ var x;", "/** @typedef {number} */ var x;"); } public void testNoStubForProvidedTypedef2() { test( "goog.provide('x.y'); /** @typedef {number} */ x.y;", "/** @const */ var x = {}; /** @typedef {number} */ x.y;"); } public void testNoStubForProvidedTypedef4() { test( "goog.provide('x.y.z'); /** @typedef {number} */ x.y.z;", "/** @const */ var x = {}; /** @const */ x.y = {}; /** @typedef {number} */ x.y.z;"); } public void testProvideRequireSameFile() { test("goog.provide('x');\ngoog.require('x');", "/** @const */ var x = {};"); } public void testDefineCases() { String jsdoc = "/** @define {number} */\n"; test(jsdoc + "goog.define('name', 1);", jsdoc + "var name = 1"); test(jsdoc + "goog.define('ns.name', 1);", jsdoc + "ns.name = 1"); } public void testDefineErrorCases() { String jsdoc = "/** @define {number} */\n"; testError("goog.define('name', 1);", MISSING_DEFINE_ANNOTATION); testError(jsdoc + "goog.define('name.2', 1);", INVALID_DEFINE_NAME_ERROR); testError(jsdoc + "goog.define();", NULL_ARGUMENT_ERROR); testError(jsdoc + "goog.define('value');", NULL_ARGUMENT_ERROR); testError(jsdoc + "goog.define(5);", INVALID_ARGUMENT_ERROR); testErrorEs6(jsdoc + "goog.define(`templateName`, 1);", INVALID_ARGUMENT_ERROR); testErrorEs6(jsdoc + "goog.define(`${template}Name`, 1);", INVALID_ARGUMENT_ERROR); } public void testDefineInExterns() { String jsdoc = "/** @define {number} */\n"; allowExternsChanges(true); testErrorExterns(jsdoc + "goog.define('value');", null); testErrorExterns("goog.define('name');", MISSING_DEFINE_ANNOTATION); testErrorExterns(jsdoc + "goog.define('name.2');", INVALID_DEFINE_NAME_ERROR); testErrorExterns(jsdoc + "goog.define();", NULL_ARGUMENT_ERROR); testErrorExterns(jsdoc + "goog.define(5);", INVALID_ARGUMENT_ERROR); } private void testErrorExterns(String externs, DiagnosticType error) { test(externs, "", null, error, null, null); } public void testDefineValues() { testSame("var CLOSURE_DEFINES = {'FOO': 'string'};"); testSame("var CLOSURE_DEFINES = {'FOO': true};"); testSame("var CLOSURE_DEFINES = {'FOO': false};"); testSame("var CLOSURE_DEFINES = {'FOO': 1};"); testSame("var CLOSURE_DEFINES = {'FOO': 0xABCD};"); testSame("var CLOSURE_DEFINES = {'FOO': -1};"); testSameEs6("let CLOSURE_DEFINES = {'FOO': 'string'};"); testSameEs6("const CLOSURE_DEFINES = {'FOO': 'string'};"); } public void testDefineValuesErrors() { testError("var CLOSURE_DEFINES = {'FOO': a};", CLOSURE_DEFINES_ERROR); testError("var CLOSURE_DEFINES = {'FOO': 0+1};", CLOSURE_DEFINES_ERROR); testError("var CLOSURE_DEFINES = {'FOO': 'value' + 'value'};", CLOSURE_DEFINES_ERROR); testError("var CLOSURE_DEFINES = {'FOO': !true};", CLOSURE_DEFINES_ERROR); testError("var CLOSURE_DEFINES = {'FOO': -true};", CLOSURE_DEFINES_ERROR); testErrorEs6("var CLOSURE_DEFINES = {SHORTHAND};", CLOSURE_DEFINES_ERROR); testErrorEs6("var CLOSURE_DEFINES = {'TEMPLATE': `template`};", CLOSURE_DEFINES_ERROR); testErrorEs6("var CLOSURE_DEFINES = {'TEMPLATE': `${template}Sub`};", CLOSURE_DEFINES_ERROR); } }