/*
* 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);
}
}