/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import static com.google.common.truth.Truth.assertThat;
/**
* Tests for {@link RuntimeTypeCheck}.
*
*/
public final class RuntimeTypeCheckTest extends CompilerTestCase {
public RuntimeTypeCheckTest() {
super("/** @const */ var undefined;");
enableTypeCheck();
}
@Override
protected void setUp() {
super.enableLineNumberCheck(false);
enableNormalize();
}
public void testValue() {
testChecks(
"/** @param {number} i */ function f(i) {}",
"/** @param {number} i */ function f(i) {"
+ " $jscomp.typecheck.checkType(i, "
+ " [$jscomp.typecheck.valueChecker('number')]);"
+ "}");
}
public void testConstValue() {
// User a variable that's immutable by the google coding convention,
// to ensure the immutable annotations are preserved.
testChecks("/** @param {number} CONST */ function f(CONST) {}",
"/** @param {number} CONST */ function f(CONST) {" +
" $jscomp.typecheck.checkType(CONST, " +
" [$jscomp.typecheck.valueChecker('number')]);" +
"}");
}
public void testValueWithInnerFn() {
testChecks("/** @param {number} i */ function f(i) { function g() {} }",
"/** @param {number} i */ function f(i) {" +
" function g() {}" +
" $jscomp.typecheck.checkType(i, " +
" [$jscomp.typecheck.valueChecker('number')]);" +
"}");
}
public void testNullValue() {
testChecks(
"/** @param {null} i */ function f(i) {}",
"/** @param {null} i */ function f(i) {"
+ " $jscomp.typecheck.checkType(i, [$jscomp.typecheck.nullChecker]);"
+ "}");
}
public void testValues() {
testChecks(
"/** @param {number} i\n@param {string} j*/ function f(i, j) {}",
"/** @param {number} i\n@param {string} j*/ function f(i, j) {"
+ " $jscomp.typecheck.checkType(i, "
+ " [$jscomp.typecheck.valueChecker('number')]);"
+ " $jscomp.typecheck.checkType(j, "
+ " [$jscomp.typecheck.valueChecker('string')]);"
+ "}");
}
public void testSkipParamOK() {
testChecks(
LINE_JOINER.join(
"/**", " * @param {*} i", " * @param {string} j", " */", "function f(i, j) {}"),
LINE_JOINER.join(
"/**",
" * @param {*} i",
" * @param {string} j",
" */",
"function f(i, j) {",
" $jscomp.typecheck.checkType(j, ",
" [$jscomp.typecheck.valueChecker('string')]);",
"}"));
}
public void testUnion() {
testChecks("/** @param {number|string} x */ function f(x) {}",
"/** @param {number|string} x */ function f(x) {" +
" $jscomp.typecheck.checkType(x, [" +
" $jscomp.typecheck.valueChecker('number'), " +
" $jscomp.typecheck.valueChecker('string')" +
" ]);" +
"}");
}
public void testUntypedParam() {
testChecksSame("/** ... */ function f(x) {}");
}
public void testReturn() {
testChecks("/** @return {string} */ function f() { return 'x'; }",
"/** @return {string} */ function f() {" +
" return $jscomp.typecheck.checkType('x', " +
" [$jscomp.typecheck.valueChecker('string')]);" +
"}");
}
public void testNativeClass() {
testChecks(
"/** @param {!String} x */ function f(x) {}",
"/** @param {!String} x */ function f(x) {"
+ " $jscomp.typecheck.checkType(x, "
+ " [$jscomp.typecheck.externClassChecker('String')]);"
+ "}");
}
public void testFunctionObjectParam() {
testChecks(
"/** @param {!Function} x */ function f(x) {}",
"/** @param {!Function} x */ function f(x) {"
+ " $jscomp.typecheck.checkType(x, "
+ " [$jscomp.typecheck.externClassChecker('Function')]);"
+ "}");
}
public void testFunctionTypeParam() {
testChecks(
"/** @param {function()} x */ function f(x) {}",
"/** @param {function()} x */ function f(x) {"
+ " $jscomp.typecheck.checkType(x, "
+ " [$jscomp.typecheck.valueChecker('function')]);"
+ "}");
}
// Closure collapses {function()|!Function} into {!Function}
public void testFunctionTypeOrFunctionObjectParam() {
testChecks(
"/** @param {function()|!Function} x */ function f(x) {}",
"/** @param {function()|!Function} x */ function f(x) {"
+ " $jscomp.typecheck.checkType(x, "
+ " [$jscomp.typecheck.externClassChecker('Function')]);"
+ "}");
}
// Closure collapses {!Function|!Object} into {!Object}
public void testFunctionObjectOrObjectParam() {
testChecks(
"/** @param {!Function|!Object} x */ function f(x) {}",
"/** @param {!Function|!Object} x */ function f(x) {"
+ " $jscomp.typecheck.checkType(x, "
+ " [$jscomp.typecheck.objectChecker]);"
+ "}");
}
public void testQualifiedClass() {
testChecks(
LINE_JOINER.join(
"var goog = {};",
"/** @constructor */",
"goog.Foo = function() {};",
"/** @param {!goog.Foo} x */ ",
"function f(x) {}"),
LINE_JOINER.join(
"var goog = {};",
"/** @constructor */",
"goog.Foo = function() {};",
"goog.Foo.prototype['instance_of__goog.Foo'] = true;",
"/** @param {!goog.Foo} x */ ",
"function f(x) {",
" $jscomp.typecheck.checkType(x, ",
" [$jscomp.typecheck.classChecker('goog.Foo')]);",
"}"));
}
public void testInnerClasses() {
testChecks(
"function f() { /** @constructor */ function inner() {} }" +
"function g() { /** @constructor */ function inner() {} }",
"function f() {" +
" /** @constructor */ function inner() {}" +
" inner.prototype['instance_of__inner'] = true;" +
"}" +
"function g() {" +
" /** @constructor */ function inner$jscomp$1() {}" +
" inner$jscomp$1.prototype['instance_of__inner$jscomp$1'] = true;" +
"}");
}
public void testInterface() {
testChecks("/** @interface */ function I() {}" +
"/** @param {!I} i */ function f(i) {}",
"/** @interface */ function I() {}" +
"/** @param {!I} i */ function f(i) {" +
" $jscomp.typecheck.checkType(i, " +
" [$jscomp.typecheck.interfaceChecker('I')])" +
"}");
}
public void testImplementedInterface() {
testChecks(
LINE_JOINER.join(
"/** @interface */ function I() {}",
"/** @param {!I} i */ function f(i) {}",
"/** @constructor\n@implements {I} */ function C() {}"),
LINE_JOINER.join(
"/** @interface */ function I() {}",
"/** @param {!I} i */ function f(i) {",
" $jscomp.typecheck.checkType(i, ",
" [$jscomp.typecheck.interfaceChecker('I')])",
"}",
"/** @constructor\n@implements {I} */ function C() {}",
"C.prototype['instance_of__C'] = true;",
"C.prototype['implements__I'] = true;"));
}
public void testExtendedInterface() {
testChecks(
LINE_JOINER.join(
"/** @interface */ function I() {}",
"/** @interface\n@extends {I} */ function J() {}",
"/** @param {!I} i */function f(i) {}",
"/** @constructor\n@implements {J} */function C() {}"),
LINE_JOINER.join(
"/** @interface */ function I() {}",
"/** @interface\n@extends {I} */ function J() {}",
"/** @param {!I} i */ function f(i) {",
" $jscomp.typecheck.checkType(i, ",
" [$jscomp.typecheck.interfaceChecker('I')])",
"}",
"/** @constructor\n@implements {J} */ function C() {}",
"C.prototype['instance_of__C'] = true;",
"C.prototype['implements__I'] = true;",
"C.prototype['implements__J'] = true;"));
}
public void testImplementedInterfaceOrdering() {
testChecks(
LINE_JOINER.join(
"/** @interface */ function I() {}" ,
"/** @param {!I} i */ function f(i) {}" ,
"/** @constructor\n@implements {I} */ function C() {}" ,
"C.prototype.f = function() {};"),
LINE_JOINER.join(
"/** @interface */ function I() {}",
"/** @param {!I} i */ function f(i) {",
" $jscomp.typecheck.checkType(i, ",
" [$jscomp.typecheck.interfaceChecker('I')])",
"}",
"/** @constructor\n@implements {I} */ function C() {}",
"C.prototype['instance_of__C'] = true;",
"C.prototype['implements__I'] = true;",
"C.prototype.f = function() {};"));
}
public void testImplementedInterfaceOrderingGoogInherits() {
testChecks(
LINE_JOINER.join(
"var goog = {};",
"goog.inherits = function(x, y) {};",
"/** @interface */function I() {}",
"/** @param {!I} i */function f(i) {}",
"/** @constructor */function B() {}",
"/** @constructor\n@extends {B}\n@implements {I} */function C() {}",
"goog.inherits(C, B);",
"C.prototype.f = function() {};"),
LINE_JOINER.join(
"var goog = {};",
"goog.inherits = function(x, y) {};",
"/** @interface */function I() {}",
"/** @param {!I} i */function f(i) {",
" $jscomp.typecheck.checkType(i, ",
" [$jscomp.typecheck.interfaceChecker('I')])",
"}",
"/** @constructor */function B() {}",
"B.prototype['instance_of__B'] = true;",
"/** @constructor\n@extends {B}\n@implements {I} */function C() {}",
"goog.inherits(C, B);",
"C.prototype['instance_of__C'] = true;",
"C.prototype['implements__I'] = true;",
"C.prototype.f = function() {};"));
}
public void testInnerConstructor() {
testChecks(
"(function() { /** @constructor */ function C() {} })()",
LINE_JOINER.join(
"(function() {",
" /** @constructor */ function C() {}",
" C.prototype['instance_of__C'] = true;",
"})()"));
}
public void testReturnNothing() {
testChecksSame("function f() { return; }");
}
public void testFunctionType() {
testChecksSame("/** @type {!Function} */function f() {}");
}
private void testChecks(String js, String expected) {
test(js, expected);
assertThat(getLastCompiler().injected).containsExactly("runtime_type_check");
}
private void testChecksSame(String js) {
testSame(js);
assertThat(getLastCompiler().injected).containsExactly("runtime_type_check");
}
@Override
protected Compiler createCompiler() {
return new NoninjectingCompiler();
}
@Override
NoninjectingCompiler getLastCompiler() {
return (NoninjectingCompiler) super.getLastCompiler();
}
@Override
protected CompilerPass getProcessor(final Compiler compiler) {
return new RuntimeTypeCheck(compiler, null);
}
@Override
protected int getNumRepetitions() {
return 1;
}
}