/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
/**
* Generate exports unit test.
*
*/
public final class GenerateExportsTest extends Es6CompilerTestCase {
private static final String EXTERNS =
"function google_exportSymbol(a, b) {}; goog.exportProperty = function(a, b, c) {}; ";
private boolean allowNonGlobalExports = true;
public GenerateExportsTest() {
super(EXTERNS);
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new GenerateExports(compiler, allowNonGlobalExports,
"google_exportSymbol", "goog.exportProperty");
}
@Override
protected int getNumRepetitions() {
// This pass only runs once.
return 1;
}
@Override
public void setUp() throws Exception {
super.setUp();
this.allowNonGlobalExports = true;
}
@Override
protected void testExternChanges(String input, String expectedExtern) {
this.enableCompareAsTree(false);
super.testExternChanges(input, expectedExtern);
}
public void testExportSymbol() {
test(
"/** @export */function foo() {}",
"/** @export */function foo(){}google_exportSymbol(\"foo\",foo)");
}
public void testExportSymbolAndProperties() {
test(
LINE_JOINER.join(
"/** @export */function foo() {}",
"/** @export */foo.prototype.bar = function() {}"),
LINE_JOINER.join(
"/** @export */function foo(){}",
"google_exportSymbol(\"foo\",foo);",
"/** @export */foo.prototype.bar=function(){};",
"goog.exportProperty(foo.prototype,\"bar\",foo.prototype.bar)"));
}
public void testExportPrototypeProperty() {
test(
LINE_JOINER.join(
"function Foo() {}",
"/** @export */ Foo.prototype.bar = function() {};"),
LINE_JOINER.join(
"function Foo() {}",
"/** @export */ Foo.prototype.bar = function(){};",
"goog.exportProperty(Foo.prototype, 'bar', Foo.prototype.bar);"));
}
public void testExportSymbolAndConstantProperties() {
test(
LINE_JOINER.join(
"/** @export */function foo() {}",
"/** @export */foo.BAR = 5;"),
LINE_JOINER.join(
"/** @export */function foo(){}",
"google_exportSymbol(\"foo\",foo);",
"/** @export */foo.BAR=5;",
"goog.exportProperty(foo,\"BAR\",foo.BAR)"));
}
public void testExportVars() {
test(
"/** @export */var FOO = 5",
LINE_JOINER.join(
"/** @export */var FOO=5;",
"google_exportSymbol('FOO',FOO)"));
}
public void testExportLet() {
testEs6(
"/** @export */let FOO = 5",
LINE_JOINER.join(
"/** @export */let FOO = 5;",
"google_exportSymbol('FOO', FOO)"));
}
public void testExportConst() {
testEs6(
"/** @export */const FOO = 5",
LINE_JOINER.join(
"/** @export */const FOO = 5;",
"google_exportSymbol('FOO', FOO)"));
}
public void testExportEs6ArrowFunction() {
testEs6(
"/** @export */var fn = ()=>{};",
LINE_JOINER.join(
"/** @export */var fn = ()=>{};",
"google_exportSymbol('fn', fn)"));
}
public void testNoExport() {
test("var FOO = 5", "var FOO=5");
}
/**
* Nested assignments are ambiguous and therefore not supported.
* @see FindExportableNodes
*/
public void testNestedVarAssign() {
this.allowNonGlobalExports = false;
testError(
LINE_JOINER.join(
"var BAR;",
"/** @export */ var FOO = BAR = 5"),
FindExportableNodes.NON_GLOBAL_ERROR);
this.allowNonGlobalExports = true;
testError(
LINE_JOINER.join(
"var BAR;",
"/** @export */ var FOO = BAR = 5"),
FindExportableNodes.EXPORT_ANNOTATION_NOT_ALLOWED);
}
/**
* Nested assignments are ambiguous and therefore not supported.
* @see FindExportableNodes
*/
public void testNestedAssign() {
this.allowNonGlobalExports = false;
testError(
LINE_JOINER.join(
"var BAR;var FOO = {};",
"/** @export */FOO.test = BAR = 5"),
FindExportableNodes.NON_GLOBAL_ERROR);
this.allowNonGlobalExports = true;
testError(
LINE_JOINER.join(
"var BAR;",
"var FOO = {};",
"/** @export */FOO.test = BAR = 5"),
FindExportableNodes.EXPORT_ANNOTATION_NOT_ALLOWED);
}
public void testNonGlobalScopeExport1() {
this.allowNonGlobalExports = false;
testError("(function() { /** @export */var FOO = 5 })()",
FindExportableNodes.NON_GLOBAL_ERROR);
this.allowNonGlobalExports = true;
testError("(function() { /** @export */var FOO = 5 })()",
FindExportableNodes.EXPORT_ANNOTATION_NOT_ALLOWED);
}
public void testNonGlobalScopeExport2() {
this.allowNonGlobalExports = false;
testError("var x = {/** @export */ A:function() {}}",
FindExportableNodes.NON_GLOBAL_ERROR);
}
public void testExportClass() {
test(
"/** @export */ function G() {} foo();",
"/** @export */ function G() {} google_exportSymbol('G', G); foo();");
}
public void testExportClassMember() {
test(
LINE_JOINER.join(
"/** @export */ function F() {}",
"/** @export */ F.prototype.method = function() {};"),
LINE_JOINER.join(
"/** @export */ function F() {}",
"google_exportSymbol('F', F);",
"/** @export */ F.prototype.method = function() {};",
"goog.exportProperty(F.prototype, 'method', F.prototype.method);"));
}
public void testExportEs6ClassSymbol() {
testEs6(
"/** @export */ class G {} foo();",
"/** @export */ class G {} google_exportSymbol('G', G); foo();");
testEs6(
"/** @export */ G = class {}; foo();",
"/** @export */ G = class {}; google_exportSymbol('G', G); foo();");
}
public void testExportEs6ClassProperty() {
testEs6(
LINE_JOINER.join(
"/** @export */ G = class {};",
"/** @export */ G.foo = class {};"),
LINE_JOINER.join(
"/** @export */ G = class {};",
"google_exportSymbol('G', G);",
"/** @public @export */ G.foo = class {};",
"goog.exportProperty(G, 'foo', G.foo)"));
testEs6(
LINE_JOINER.join(
"G = class {};",
"/** @export */ G.prototype.foo = class {};"),
LINE_JOINER.join(
"G = class {};",
"/** @export @public */ G.prototype.foo = class {};",
"goog.exportProperty(G.prototype, 'foo', G.prototype.foo)"));
}
public void testExportEs6ClassMembers() {
testEs6(
LINE_JOINER.join(
"/** @export */",
"class G {",
" /** @export */ method() {}",
"}"),
LINE_JOINER.join(
"/** @export @public */ class G { /** @export */ method() {} }",
"google_exportSymbol('G', G);",
"goog.exportProperty(G.prototype, 'method', G.prototype.method);"));
testEs6(
LINE_JOINER.join(
"/** @export */",
"class G {",
" /** @export */ static method() {}",
"}"),
LINE_JOINER.join(
"/** @export @public */ class G { /** @export */ static method() {} }",
"google_exportSymbol('G', G);",
"goog.exportProperty(G, 'method', G.method);"));
}
public void testGoogScopeFunctionOutput() {
test(
"/** @export */ $jscomp.scope.foo = /** @export */ function() {}",
LINE_JOINER.join(
"/** @export */ $jscomp.scope.foo = /** @export */ function() {};",
"google_exportSymbol('$jscomp.scope.foo', $jscomp.scope.foo);"));
}
public void testGoogScopeClassOutput() {
testEs6(
"/** @export */ $jscomp.scope.foo = /** @export */ class {}",
LINE_JOINER.join(
"/** @export */ $jscomp.scope.foo = /** @export */ class {};",
"google_exportSymbol('$jscomp.scope.foo', $jscomp.scope.foo);"));
}
public void testExportSubclass() {
test(
LINE_JOINER.join(
"var goog = {}; function F() {}",
"/** @export */ function G() {} goog.inherits(G, F);"),
LINE_JOINER.join(
"var goog = {}; function F() {}",
"/** @export */ function G() {} goog.inherits(G, F); google_exportSymbol('G', G);"));
}
public void testExportEnum() {
// TODO(johnlenz): Issue 310, should the values also be externed?
test("/** @enum {string}\n @export */ var E = {A:1, B:2};",
"/** @enum {string}\n @export */ var E = {A:1, B:2}; google_exportSymbol('E', E);");
}
public void testExportObjectLit1() {
allowExternsChanges(true);
String code = "var E = {/** @export */ A:1, B:2};";
testSame(code);
testExternChanges(code, "Object.prototype.A;");
}
public void testExportObjectLit2() {
allowExternsChanges(true);
String code = "var E = {/** @export */ get A() { return 1 }, B:2};";
testSame(code);
testExternChanges(code, "Object.prototype.A;");
}
public void testExportObjectLit3() {
allowExternsChanges(true);
String code = "var E = {/** @export */ set A(v) {}, B:2};";
testSame(code);
testExternChanges(code, "Object.prototype.A;");
}
public void testExportObjectLit4() {
allowExternsChanges(true);
String code = "var E = {/** @export */ A:function() {}, B:2};";
testSame(code);
testExternChanges(code, "Object.prototype.A;");
}
public void testExportClassMember1() {
allowExternsChanges(true);
String code = "var E = function() { /** @export */ this.foo = 1; };";
testSame(code);
testExternChanges(code, "Object.prototype.foo;");
}
public void testMemberExportDoesntConflict() {
allowExternsChanges(true);
String code = LINE_JOINER.join(
"var foo = function() { /** @export */ this.foo = 1; };",
"/** @export */ foo.method = function(){};");
String result = LINE_JOINER.join(
"var foo = function() { /** @export */ this.foo = 1; };",
"/** @export */ foo.method = function(){};",
"google_exportSymbol('foo.method', foo.method);");
test(code, result);
testExternChanges(code, "Object.prototype.foo;");
}
public void testExportClassMemberStub() {
allowExternsChanges(true);
String code = "var E = function() { /** @export */ this.foo; };";
testSame(code);
testExternChanges(code, "Object.prototype.foo;");
}
}