489 lines
17 KiB
Java
489 lines
17 KiB
Java
|
/*
|
||
|
* Copyright 2009 The Closure Compiler Authors.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package com.google.javascript.jscomp;
|
||
|
|
||
|
|
||
|
import com.google.common.base.Joiner;
|
||
|
import com.google.common.collect.Lists;
|
||
|
|
||
|
import junit.framework.TestCase;
|
||
|
|
||
|
import java.util.List;
|
||
|
|
||
|
/**
|
||
|
* Tests for {@link ExternExportsPass}.
|
||
|
*
|
||
|
*/
|
||
|
public class ExternExportsPassTest extends TestCase {
|
||
|
|
||
|
private boolean runCheckTypes = true;
|
||
|
|
||
|
/**
|
||
|
* ExternExportsPass relies on type information to emit JSDoc annotations for
|
||
|
* exported externs. However, the user can disable type checking and still
|
||
|
* ask for externs to be exported. Set this flag to enable or disable checking
|
||
|
* of types during a test.
|
||
|
*/
|
||
|
private void setRunCheckTypes(boolean shouldRunCheckTypes) {
|
||
|
runCheckTypes = shouldRunCheckTypes;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setUp() throws Exception {
|
||
|
super.setUp();
|
||
|
|
||
|
setRunCheckTypes(true);
|
||
|
}
|
||
|
|
||
|
public void testExportSymbol() throws Exception {
|
||
|
compileAndCheck("var a = {}; a.b = {}; a.b.c = function(d, e, f) {};" +
|
||
|
"goog.exportSymbol('foobar', a.b.c)",
|
||
|
"/**\n" +
|
||
|
" * @param {*} d\n" +
|
||
|
" * @param {*} e\n" +
|
||
|
" * @param {*} f\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"var foobar = function(d, e, f) {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportSymbolDefinedInVar() throws Exception {
|
||
|
compileAndCheck("var a = function(d, e, f) {};" +
|
||
|
"goog.exportSymbol('foobar', a)",
|
||
|
"/**\n" +
|
||
|
" * @param {*} d\n" +
|
||
|
" * @param {*} e\n" +
|
||
|
" * @param {*} f\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"var foobar = function(d, e, f) {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportProperty() throws Exception {
|
||
|
compileAndCheck("var a = {}; a.b = {}; a.b.c = function(d, e, f) {};" +
|
||
|
"goog.exportProperty(a.b, 'cprop', a.b.c)",
|
||
|
"var a;\n" +
|
||
|
"a.b;\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {*} d\n" +
|
||
|
" * @param {*} e\n" +
|
||
|
" * @param {*} f\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"a.b.cprop = function(d, e, f) {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportMultiple() throws Exception {
|
||
|
compileAndCheck("var a = {}; a.b = function(p1) {}; " +
|
||
|
"a.b.c = function(d, e, f) {};" +
|
||
|
"a.b.prototype.c = function(g, h, i) {};" +
|
||
|
"goog.exportSymbol('a.b', a.b);" +
|
||
|
"goog.exportProperty(a.b, 'c', a.b.c);" +
|
||
|
"goog.exportProperty(a.b.prototype, 'c', a.b.prototype.c);",
|
||
|
|
||
|
"var a;\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {*} p1\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"a.b = function(p1) {\n};\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {*} d\n" +
|
||
|
" * @param {*} e\n" +
|
||
|
" * @param {*} f\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"a.b.c = function(d, e, f) {\n};\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {*} g\n" +
|
||
|
" * @param {*} h\n" +
|
||
|
" * @param {*} i\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"a.b.prototype.c = function(g, h, i) {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportMultiple2() throws Exception {
|
||
|
compileAndCheck("var a = {}; a.b = function(p1) {}; " +
|
||
|
"a.b.c = function(d, e, f) {};" +
|
||
|
"a.b.prototype.c = function(g, h, i) {};" +
|
||
|
"goog.exportSymbol('hello', a);" +
|
||
|
"goog.exportProperty(a.b, 'c', a.b.c);" +
|
||
|
"goog.exportProperty(a.b.prototype, 'c', a.b.prototype.c);",
|
||
|
|
||
|
"/** @type {{b: function (?): undefined}} */\n" +
|
||
|
"var hello = {};\n" +
|
||
|
"hello.b;\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {*} d\n" +
|
||
|
" * @param {*} e\n" +
|
||
|
" * @param {*} f\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"hello.b.c = function(d, e, f) {\n};\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {*} g\n" +
|
||
|
" * @param {*} h\n" +
|
||
|
" * @param {*} i\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"hello.b.prototype.c = function(g, h, i) {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportMultiple3() throws Exception {
|
||
|
compileAndCheck("var a = {}; a.b = function(p1) {}; " +
|
||
|
"a.b.c = function(d, e, f) {};" +
|
||
|
"a.b.prototype.c = function(g, h, i) {};" +
|
||
|
"goog.exportSymbol('prefix', a.b);" +
|
||
|
"goog.exportProperty(a.b, 'c', a.b.c);",
|
||
|
|
||
|
"/**\n" +
|
||
|
" * @param {*} p1\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"var prefix = function(p1) {\n};\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {*} d\n" +
|
||
|
" * @param {*} e\n" +
|
||
|
" * @param {*} f\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"prefix.c = function(d, e, f) {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportNonStaticSymbol() throws Exception {
|
||
|
compileAndCheck("var a = {}; a.b = {}; var d = {}; a.b.c = d;" +
|
||
|
"goog.exportSymbol('foobar', a.b.c)",
|
||
|
"var foobar;\n");
|
||
|
}
|
||
|
|
||
|
public void testExportNonStaticSymbol2() throws Exception {
|
||
|
compileAndCheck("var a = {}; a.b = {}; var d = null; a.b.c = d;" +
|
||
|
"goog.exportSymbol('foobar', a.b.c())",
|
||
|
"var foobar;\n");
|
||
|
}
|
||
|
|
||
|
public void testExportNonexistentProperty() throws Exception {
|
||
|
compileAndCheck("var a = {}; a.b = {}; a.b.c = function(d, e, f) {};" +
|
||
|
"goog.exportProperty(a.b, 'none', a.b.none)",
|
||
|
"var a;\n" +
|
||
|
"a.b;\n" +
|
||
|
"a.b.none;\n");
|
||
|
}
|
||
|
|
||
|
public void testExportSymbolWithTypeAnnotation() {
|
||
|
|
||
|
compileAndCheck("var internalName;\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {string} param1\n" +
|
||
|
" * @param {number} param2\n" +
|
||
|
" * @return {string}\n" +
|
||
|
" */\n" +
|
||
|
"internalName = function(param1, param2) {" +
|
||
|
"return param1 + param2;" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('externalName', internalName)",
|
||
|
"/**\n" +
|
||
|
" * @param {string} param1\n" +
|
||
|
" * @param {number} param2\n" +
|
||
|
" * @return {string}\n" +
|
||
|
" */\n" +
|
||
|
"var externalName = function(param1, param2) {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportSymbolWithoutTypeCheck() {
|
||
|
// ExternExportsPass should not emit annotations
|
||
|
// if there is no type information available.
|
||
|
setRunCheckTypes(false);
|
||
|
|
||
|
compileAndCheck("var internalName;\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {string} param1\n" +
|
||
|
" * @param {number} param2\n" +
|
||
|
" * @return {string}\n" +
|
||
|
" */\n" +
|
||
|
"internalName = function(param1, param2) {" +
|
||
|
"return param1 + param2;" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('externalName', internalName)",
|
||
|
"var externalName = function(param1, param2) {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportSymbolWithConstructor() {
|
||
|
compileAndCheck("var internalName;\n" +
|
||
|
"/**\n" +
|
||
|
" * @constructor\n" +
|
||
|
" */\n" +
|
||
|
"internalName = function() {" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('externalName', internalName)",
|
||
|
"/**\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" * @constructor\n" +
|
||
|
" */\n" +
|
||
|
"var externalName = function() {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportSymbolWithConstructorWithoutTypeCheck() {
|
||
|
// For now, skipping type checking should prevent generating
|
||
|
// annotations of any kind, so, e.g., @constructor is not preserved.
|
||
|
// This is probably not ideal, but since JSDocInfo for functions is attached
|
||
|
// to JSTypes and not Nodes (and no JSTypes are created when checkTypes
|
||
|
// is false), we don't really have a choice.
|
||
|
|
||
|
setRunCheckTypes(false);
|
||
|
|
||
|
compileAndCheck("var internalName;\n" +
|
||
|
"/**\n" +
|
||
|
" * @constructor\n" +
|
||
|
" */\n" +
|
||
|
"internalName = function() {" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('externalName', internalName)",
|
||
|
"var externalName = function() {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportFunctionWithOptionalArguments() {
|
||
|
compileAndCheck("var internalName;\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {number=} a\n" +
|
||
|
" */\n" +
|
||
|
"internalName = function(a) {" +
|
||
|
" return 6;\n" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('externalName', internalName)",
|
||
|
"/**\n" +
|
||
|
" * @param {number=} a\n" +
|
||
|
" */\n" +
|
||
|
"var externalName = function(a) {\n};\n");
|
||
|
}
|
||
|
|
||
|
public void testExportFunctionWithVariableArguments() {
|
||
|
compileAndCheck("var internalName;\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {...number} a\n" +
|
||
|
" * @return {number}\n" +
|
||
|
" */\n" +
|
||
|
"internalName = function(a) {" +
|
||
|
" return 6;\n" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('externalName', internalName)",
|
||
|
"/**\n" +
|
||
|
" * @param {...number} a\n" +
|
||
|
" * @return {number}\n" +
|
||
|
" */\n" +
|
||
|
"var externalName = function(a) {\n};\n");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enums are not currently handled.
|
||
|
*/
|
||
|
public void testExportEnum() {
|
||
|
// We don't care what the values of the object properties are.
|
||
|
// They're ignored by the type checker, and even if they weren't, it'd
|
||
|
// be incomputable to get them correct in all cases
|
||
|
// (think complex objects).
|
||
|
compileAndCheck(
|
||
|
"/** @enum {string}\n @export */ var E = {A:8, B:9};" +
|
||
|
"goog.exportSymbol('E', E);",
|
||
|
"/** @enum {string} */\n" +
|
||
|
"var E = {A:1, B:2};\n");
|
||
|
}
|
||
|
|
||
|
/** If we export a property with "prototype" as a path component, there
|
||
|
* is no need to emit the initializer for prototype because every namespace
|
||
|
* has one automatically.
|
||
|
*/
|
||
|
public void testExportDontEmitPrototypePathPrefix() {
|
||
|
compileAndCheck(
|
||
|
"/**\n" +
|
||
|
" * @constructor\n" +
|
||
|
" */\n" +
|
||
|
"var Foo = function() {};" +
|
||
|
"/**\n" +
|
||
|
" * @return {number}\n" +
|
||
|
" */\n" +
|
||
|
"Foo.prototype.m = function() {return 6;};\n" +
|
||
|
"goog.exportSymbol('Foo', Foo);\n" +
|
||
|
"goog.exportProperty(Foo.prototype, 'm', Foo.prototype.m);",
|
||
|
"/**\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" * @constructor\n" +
|
||
|
" */\n" +
|
||
|
"var Foo = function() {\n};\n" +
|
||
|
"/**\n" +
|
||
|
" * @return {number}\n" +
|
||
|
" */\n" +
|
||
|
"Foo.prototype.m = function() {\n};\n"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Test the workflow of creating an externs file for a library
|
||
|
* via the export pass and then using that externs file in a client.
|
||
|
*
|
||
|
* There should be no warnings in the client if the library includes
|
||
|
* type information for the exported functions and the client uses them
|
||
|
* correctly.
|
||
|
*/
|
||
|
public void testUseExportsAsExterns() {
|
||
|
String librarySource =
|
||
|
"/**\n" +
|
||
|
" * @param {number} a\n" +
|
||
|
" * @constructor\n" +
|
||
|
" */\n" +
|
||
|
"var InternalName = function(a) {" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('ExternalName', InternalName)";
|
||
|
|
||
|
String clientSource =
|
||
|
"var a = new ExternalName(6);\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {ExternalName} x\n" +
|
||
|
" */\n" +
|
||
|
"var b = function(x) {};";
|
||
|
|
||
|
Result libraryCompileResult = compileAndExportExterns(librarySource);
|
||
|
|
||
|
assertEquals(0, libraryCompileResult.warnings.length);
|
||
|
assertEquals(0, libraryCompileResult.errors.length);
|
||
|
|
||
|
String generatedExterns = libraryCompileResult.externExport;
|
||
|
|
||
|
Result clientCompileResult = compileAndExportExterns(clientSource,
|
||
|
generatedExterns);
|
||
|
|
||
|
assertEquals(0, clientCompileResult.warnings.length);
|
||
|
assertEquals(0, clientCompileResult.errors.length);
|
||
|
}
|
||
|
|
||
|
public void testWarnOnExportFunctionWithUnknownReturnType() {
|
||
|
String librarySource =
|
||
|
"var InternalName = function() {" +
|
||
|
" return 6;" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('ExternalName', InternalName)";
|
||
|
|
||
|
Result libraryCompileResult = compileAndExportExterns(librarySource);
|
||
|
|
||
|
assertEquals(1, libraryCompileResult.warnings.length);
|
||
|
assertEquals(0, libraryCompileResult.errors.length);
|
||
|
}
|
||
|
|
||
|
public void testDontWarnOnExportConstructorWithUnknownReturnType() {
|
||
|
String librarySource =
|
||
|
"/**\n" +
|
||
|
" * @constructor\n" +
|
||
|
" */\n " +
|
||
|
"var InternalName = function() {" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('ExternalName', InternalName)";
|
||
|
|
||
|
Result libraryCompileResult = compileAndExportExterns(librarySource);
|
||
|
|
||
|
assertEquals(0, libraryCompileResult.warnings.length);
|
||
|
assertEquals(0, libraryCompileResult.errors.length);
|
||
|
}
|
||
|
|
||
|
public void testTypedef() {
|
||
|
compileAndCheck(
|
||
|
"/** @typedef {{x: number, y: number}} */ var Coord;\n" +
|
||
|
"/**\n" +
|
||
|
" * @param {Coord} a\n" +
|
||
|
" * @export\n" +
|
||
|
" */\n" +
|
||
|
"var fn = function(a) {};" +
|
||
|
"goog.exportSymbol('fn', fn);",
|
||
|
"/**\n" +
|
||
|
" * @param {{x: number, y: number}} a\n" +
|
||
|
" * @return {undefined}\n" +
|
||
|
" */\n" +
|
||
|
"var fn = function(a) {\n};\n");
|
||
|
}
|
||
|
|
||
|
private void compileAndCheck(String js, String expected) {
|
||
|
Result result = compileAndExportExterns(js);
|
||
|
|
||
|
assertEquals(expected, result.externExport);
|
||
|
}
|
||
|
|
||
|
public void testWarnOnExportFunctionWithUnknownParameterTypes() {
|
||
|
/* This source is missing types for the b and c parameters */
|
||
|
String librarySource =
|
||
|
"/**\n" +
|
||
|
" * @param {number} a\n" +
|
||
|
" * @return {number}" +
|
||
|
" */\n " +
|
||
|
"var InternalName = function(a,b,c) {" +
|
||
|
" return 6;" +
|
||
|
"};" +
|
||
|
"goog.exportSymbol('ExternalName', InternalName)";
|
||
|
|
||
|
Result libraryCompileResult = compileAndExportExterns(librarySource);
|
||
|
|
||
|
assertEquals(2, libraryCompileResult.warnings.length);
|
||
|
assertEquals(0, libraryCompileResult.errors.length);
|
||
|
}
|
||
|
|
||
|
private Result compileAndExportExterns(String js) {
|
||
|
return compileAndExportExterns(js, "");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compiles the passed in JavaScript with the passed in externs and returns
|
||
|
* the new externs exported by the this pass.
|
||
|
*
|
||
|
* @param js the source to be compiled
|
||
|
* @param externs the externs the {@code js} source needs
|
||
|
* @return the externs generated from {@code js}
|
||
|
*/
|
||
|
private Result compileAndExportExterns(String js, String externs) {
|
||
|
Compiler compiler = new Compiler();
|
||
|
CompilerOptions options = new CompilerOptions();
|
||
|
options.externExportsPath = "externs.js";
|
||
|
|
||
|
// Turn on IDE mode to get rid of optimizations.
|
||
|
options.ideMode = true;
|
||
|
|
||
|
/* Check types so we can make sure our exported externs have
|
||
|
* type information.
|
||
|
*/
|
||
|
options.checkSymbols = true;
|
||
|
options.checkTypes = runCheckTypes;
|
||
|
|
||
|
List<SourceFile> inputs = Lists.newArrayList(
|
||
|
SourceFile.fromCode("testcode",
|
||
|
"var goog = {};" +
|
||
|
"goog.exportSymbol = function(a, b) {}; " +
|
||
|
"goog.exportProperty = function(a, b, c) {}; " +
|
||
|
js));
|
||
|
|
||
|
List<SourceFile> externFiles = Lists.newArrayList(
|
||
|
SourceFile.fromCode("externs", externs));
|
||
|
|
||
|
Result result = compiler.compile(externFiles, inputs, options);
|
||
|
|
||
|
if (!result.success) {
|
||
|
String msg = "Errors:";
|
||
|
msg += Joiner.on("\n").join(result.errors);
|
||
|
assertTrue(msg, result.success);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
}
|