/*
* 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;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
/**
* Tests for {@link CheckMissingReturn}.
*
*/
public final class CheckMissingReturnTest extends CompilerTestCase {
public CheckMissingReturnTest() {
enableTypeCheck();
}
@Override
protected CompilerPass getProcessor(final Compiler compiler) {
return new CombinedCompilerPass(compiler, new CheckMissingReturn(compiler));
}
public void testMissingReturn() {
// Requires control flow analysis.
testMissing("if (a) { return 1; }");
// Switch statement.
testMissing("switch(1) { case 12: return 5; }");
// Test try catch finally.
testMissing("try { foo() } catch (e) { return 5; } finally { }");
// Nested scope.
testMissing("/** @return {number} */ function f() { var x; }; return 1;");
testMissing("/** @return {number} */ function f() { return 1; };");
}
public void testReturnNotMissing() {
// Empty function body. Ignore this case. The remainder of the functions in
// this test have non-empty bodies.
testNotMissing("");
// Simple cases.
testSame("function f() { var x; }");
testNotMissing("return 1;");
// Returning void and undefined.
testNotMissing("void", "var x;");
testNotMissing("undefined", "var x;");
// Returning a union that includes void or undefined.
testNotMissing("number|undefined", "var x;");
testNotMissing("number|void", "var x;");
testNotMissing("*", "var x;");
// Test try catch finally.
testNotMissing("try { return foo() } catch (e) { } finally { }");
testNotMissing("try {throw e;} catch (e) { return foo() } finally { }");
testNotMissing("try {} catch (e) {} finally {return foo()};");
// Nested function.
testNotMissing(
"/** @return {number} */ function f() { return 1; }; return 1;");
// Strange tests that come up when reviewing closure code.
testNotMissing("try { return 12; } finally { return 62; }");
testNotMissing("try { } finally { return 1; }");
testNotMissing("switch(1) { default: return 1; }");
testNotMissing("switch(g) { case 1: return 1; default: return 2; }");
}
public void testFinallyStatements() {
// The control flow analysis (CFA) treats finally blocks somewhat strangely.
// The CFA might indicate that a finally block implicitly returns. However,
// if entry into the finally block is normally caused by an explicit return
// statement, then a return statement isn't missing:
//
// try {
// return 1;
// } finally {
// // CFA determines implicit return. However, return not missing
// // because of return statement in try block.
// }
//
// Hence extra tests are warranted for various cases involving finally
// blocks.
// Simple finally case.
testNotMissing("try { return 1; } finally { }");
testNotMissing("try { } finally { return 1; }");
testMissing("try { } finally { }");
// Cycles in the CFG within the finally block were causing problems before.
testNotMissing("try { return 1; } finally { while (true) { } }");
testMissing("try { } finally { while (x) { } }");
testMissing("try { } finally { while (x) { if (x) { break; } } }");
testNotMissing(
"try { return 2; } finally { while (x) { if (x) { break; } } }");
// Test various cases with nested try statements.
testMissing("try { } finally { try { } finally { } }");
testNotMissing("try { } finally { try { return 1; } finally { } }");
testNotMissing("try { return 1; } finally { try { } finally { } }");
// Calling a function potentially causes control flow to transfer to finally
// block. However, the function will not return in this case as the
// exception will unwind the stack. Hence this function isn't missing a
// return statement (i.e., the running program will not expect a return
// value from the function if an exception is thrown).
testNotMissing("try { g(); return 1; } finally { }");
// Closures within try ... finally affect missing return statement analysis
// because of the nested scopes. The following tests check for missing
// return statements in the three possible configurations: both scopes
// return; enclosed doesn't return; enclosing doesn't return.
testNotMissing(
"try {" +
" /** @return {number} */ function f() {" +
" try { return 1; }" +
" finally { }" +
" };" +
" return 1;" +
"}" +
"finally { }");
testMissing(
"try {" +
" /** @return {number} */ function f() {" +
" try { }" +
" finally { }" +
" };" +
" return 1;" +
"}" +
"finally { }");
testMissing(
"try {" +
" /** @return {number} */ function f() {" +
" try { return 1; }" +
" finally { }" +
" };" +
"}" +
"finally { }");
}
public void testKnownConditions() {
testNotMissing("if (true) return 1");
testMissing("if (true) {} else {return 1}");
testMissing("if (false) return 1");
testNotMissing("if (false) {} else {return 1}");
testNotMissing("if (1) return 1");
testMissing("if (1) {} else {return 1}");
testMissing("if (0) return 1");
testNotMissing("if (0) {} else {return 1}");
testNotMissing("if (3) return 1");
testMissing("if (3) {} else {return 1}");
}
public void testKnownWhileLoop() {
testNotMissing("while (1) return 1");
testNotMissing("while (1) { if (x) {return 1} else {return 1}}");
testNotMissing("while (0) {} return 1");
// TODO(user): The current algorithm will not detect this case. It is
// still computable in most cases.
testNotMissing("while (1) {} return 0");
testMissing("while (false) return 1");
// Not known.
testMissing("while(x) { return 1 }");
}
public void testMultiConditions() {
testMissing("if (a) { } else { while (1) {return 1} }");
testNotMissing("if (a) { return 1} else { while (1) {return 1} }");
}
public void testIssue779() {
testNotMissing(
"var a = f(); try { alert(); if (a > 0) return 1; }" +
"finally { a = 5; } return 2;");
}
public void testConstructors() {
testSame("/** @constructor */ function foo() {} ");
final String constructorWithReturn = "/** @constructor \n" +
" * @return {!foo} */ function foo() {" +
" if (!(this instanceof foo)) { return new foo; } }";
testSame(constructorWithReturn);
}
public void testClosureAsserts() {
String closureDefs =
"/** @const */ var goog = {};\n" +
"goog.asserts = {};\n" +
"goog.asserts.fail = function(x) {};";
testNotMissing(closureDefs + "goog.asserts.fail('');");
testNotMissing(closureDefs
+ "switch (x) { case 1: return 1; default: goog.asserts.fail(''); }");
}
public void testInfiniteLoops() {
testNotMissing("while (true) { x = y; if (x === 0) { return 1; } }");
testNotMissing("for (;true;) { x = y; if (x === 0) { return 1; } }");
testNotMissing("for (;;) { x = y; if (x === 0) { return 1; } }");
}
private static String createFunction(String returnType, String body) {
return "/** @return {" + returnType + "} */ function foo() {" + body + "}";
}
private static String createShorthandFunctionInObjLit(
String returnType, String body) {
return LINE_JOINER.join(
"var obj = {",
" /** @return {" + returnType + "} */",
" foo() {", body, "}",
"}");
}
private void testMissingInTraditionalFunction(String returnType, String body) {
String js = createFunction(returnType, body);
testWarning(js, CheckMissingReturn.MISSING_RETURN_STATEMENT);
}
private void testNotMissingInTraditionalFunction(String returnType, String body) {
testSame(createFunction(returnType, body));
}
private void testMissingInShorthandFunction(String returnType, String body) {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
String js = createShorthandFunctionInObjLit(returnType, body);
testWarning(js, CheckMissingReturn.MISSING_RETURN_STATEMENT);
}
private void testNotMissingInShorthandFunction(String returnType, String body) {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
testSame(createShorthandFunctionInObjLit(returnType, body));
}
private void testMissing(String returnType, String body) {
testMissingInTraditionalFunction(returnType, body);
testMissingInShorthandFunction(returnType, body);
}
private void testNotMissing(String returnType, String body) {
testNotMissingInTraditionalFunction(returnType, body);
testNotMissingInShorthandFunction(returnType, body);
}
/** Creates function with return type {number} */
private void testNotMissing(String body) {
testNotMissing("number", body);
}
/** Creates function with return type {number} */
private void testMissing(String body) {
testMissing("number", body);
}
}