/*
* Copyright 2015 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.THROW_ASSERTION_ERROR;
import static com.google.javascript.jscomp.testing.NodeSubject.assertNode;
import static com.google.javascript.rhino.Token.ANY_TYPE;
import static com.google.javascript.rhino.Token.ARRAY_TYPE;
import static com.google.javascript.rhino.Token.BOOLEAN_TYPE;
import static com.google.javascript.rhino.Token.FUNCTION_TYPE;
import static com.google.javascript.rhino.Token.NAMED_TYPE;
import static com.google.javascript.rhino.Token.NUMBER_TYPE;
import static com.google.javascript.rhino.Token.PARAMETERIZED_TYPE;
import static com.google.javascript.rhino.Token.RECORD_TYPE;
import static com.google.javascript.rhino.Token.STRING_TYPE;
import static com.google.javascript.rhino.TypeDeclarationsIR.anyType;
import static com.google.javascript.rhino.TypeDeclarationsIR.arrayType;
import static com.google.javascript.rhino.TypeDeclarationsIR.booleanType;
import static com.google.javascript.rhino.TypeDeclarationsIR.functionType;
import static com.google.javascript.rhino.TypeDeclarationsIR.namedType;
import static com.google.javascript.rhino.TypeDeclarationsIR.numberType;
import static com.google.javascript.rhino.TypeDeclarationsIR.parameterizedType;
import static com.google.javascript.rhino.TypeDeclarationsIR.recordType;
import static com.google.javascript.rhino.TypeDeclarationsIR.stringType;
import static com.google.javascript.rhino.TypeDeclarationsIR.unionType;
import static java.util.Arrays.asList;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.JsdocToEs6TypedConverter.TypeDeclarationsIRFactory;
import com.google.javascript.jscomp.parsing.JsDocInfoParser;
import com.google.javascript.jscomp.testing.NodeSubject;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Node.TypeDeclarationNode;
import java.util.LinkedHashMap;
/**
* Tests the conversion of closure-style type declarations in JSDoc
* to inline type declarations, by running both syntaxes through the parser
* and verifying the resulting AST is the same.
*/
public final class JsdocToEs6TypedConverterTest extends CompilerTestCase {
@Override
public void setUp() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT6_TYPED);
compareJsDoc = false;
}
@Override
protected CompilerOptions getOptions() {
CompilerOptions options = super.getOptions();
options.setLanguageOut(LanguageMode.ECMASCRIPT6_TYPED);
return options;
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new JsdocToEs6TypedConverter(compiler);
}
@Override
protected int getNumRepetitions() {
return 1;
}
public void testVariableDeclaration() {
test("/** @type {string} */ var print;", "var print: string;");
}
public void testVariableDeclarationWithoutDeclaredType() throws Exception {
testSame("var print;");
}
public void testFunctionReturnType() throws Exception {
test("/** @return {boolean} */ function b(){}", "function b(): boolean {}");
}
public void testFunctionParameterTypes() throws Exception {
test("/** @param {number} n @param {string} s */ function t(n,s){}",
"function t(n: number, s: string) {}");
}
public void testFunctionInsideAssignment() throws Exception {
test("/** @param {boolean} b @return {boolean} */ "
+ "var f = function(b){return !b};",
"var f = function(b: boolean): boolean { return !b; };");
}
public void testNestedFunctions() throws Exception {
test("/**@param {boolean} b*/ "
+ "var f = function(b){var t = function(l) {}; t();};",
"var f = function(b: boolean) {"
+ " var t = function(l) {"
+ " };"
+ " t();"
+ "};");
}
public void testUnknownType() throws Exception {
test("/** @type {?} */ var n;", "var n: any;");
}
// TypeScript doesn't have a representation for the Undefined type,
// so our transpilation is lossy here.
public void testUndefinedType() throws Exception {
test("/** @type {undefined} */ var n;", "var n;");
}
public void testConvertSimpleTypes() {
assertParseTypeAndConvert("?").hasType(ANY_TYPE);
assertParseTypeAndConvert("*").hasType(ANY_TYPE);
assertParseTypeAndConvert("boolean").hasType(BOOLEAN_TYPE);
assertParseTypeAndConvert("number").hasType(NUMBER_TYPE);
assertParseTypeAndConvert("string").hasType(STRING_TYPE);
}
public void testConvertNamedTypes() throws Exception {
assertParseTypeAndConvert("Window")
.isEqualTo(namedType("Window"));
assertParseTypeAndConvert("goog.ui.Menu")
.isEqualTo(namedType("goog.ui.Menu"));
assertNode(namedType("goog.ui.Menu"))
.isEqualTo(new TypeDeclarationNode(NAMED_TYPE,
IR.getprop(IR.getprop(IR.name("goog"), IR.string("ui")), IR.string("Menu"))));
}
public void testConvertTypeApplication() throws Exception {
assertParseTypeAndConvert("Array.<string>")
.isEqualTo(arrayType(stringType()));
assertParseTypeAndConvert("Object.<string, number>")
.isEqualTo(parameterizedType(namedType("Object"), asList(stringType(), numberType())));
assertNode(parameterizedType(namedType("Array"), asList(stringType())))
.isEqualTo(new TypeDeclarationNode(PARAMETERIZED_TYPE,
new TypeDeclarationNode(NAMED_TYPE, IR.name("Array")),
new TypeDeclarationNode(STRING_TYPE)));
}
public void testConvertTypeUnion() throws Exception {
assertParseTypeAndConvert("(number|boolean)")
.isEqualTo(unionType(numberType(), booleanType()));
}
public void testConvertRecordType() throws Exception {
LinkedHashMap<String, TypeDeclarationNode> properties = new LinkedHashMap<>();
properties.put("myNum", numberType());
properties.put("myObject", null);
assertParseTypeAndConvert("{myNum: number, myObject}")
.isEqualTo(recordType(properties));
}
public void testCreateRecordType() throws Exception {
LinkedHashMap<String, TypeDeclarationNode> properties = new LinkedHashMap<>();
properties.put("myNum", numberType());
properties.put("myObject", null);
TypeDeclarationNode node = recordType(properties);
Node prop1 = IR.stringKey("myNum");
prop1.addChildToFront(new TypeDeclarationNode(NUMBER_TYPE));
Node prop2 = IR.stringKey("myObject");
assertNode(node)
.isEqualTo(new TypeDeclarationNode(RECORD_TYPE, prop1, prop2));
}
public void testConvertRecordTypeWithTypeApplication() throws Exception {
Node prop1 = IR.stringKey("length");
assertParseTypeAndConvert("Array.<{length}>")
.isEqualTo(new TypeDeclarationNode(ARRAY_TYPE,
new TypeDeclarationNode(RECORD_TYPE, prop1)));
}
public void testConvertNullableType() throws Exception {
assertParseTypeAndConvert("?number")
.isEqualTo(numberType());
}
// TODO(alexeagle): change this test once we can capture nullability constraints in TypeScript
public void testConvertNonNullableType() throws Exception {
assertParseTypeAndConvert("!Object")
.isEqualTo(namedType("Object"));
}
public void testConvertFunctionType() throws Exception {
Node p1 = IR.name("p1");
p1.setDeclaredTypeExpression(stringType());
Node p2 = IR.name("p2");
p2.setDeclaredTypeExpression(booleanType());
assertParseTypeAndConvert("function(string, boolean)")
.isEqualTo(new TypeDeclarationNode(FUNCTION_TYPE, anyType(), p1, p2));
}
public void testConvertFunctionReturnType() throws Exception {
assertParseTypeAndConvert("function(): number")
.isEqualTo(new TypeDeclarationNode(FUNCTION_TYPE, numberType()));
}
public void testConvertFunctionThisType() throws Exception {
Node p1 = IR.name("p1");
p1.setDeclaredTypeExpression(stringType());
assertParseTypeAndConvert("function(this:goog.ui.Menu, string)")
.isEqualTo(new TypeDeclarationNode(FUNCTION_TYPE, anyType(), p1));
}
public void testConvertFunctionNewType() throws Exception {
Node p1 = IR.name("p1");
p1.setDeclaredTypeExpression(stringType());
assertParseTypeAndConvert("function(new:goog.ui.Menu, string)")
.isEqualTo(new TypeDeclarationNode(FUNCTION_TYPE, anyType(), p1));
}
public void testConvertVariableParameters() throws Exception {
Node p1 = IR.name("p1");
p1.setDeclaredTypeExpression(stringType());
Node p2 = IR.rest("p2");
p2.setDeclaredTypeExpression(arrayType(numberType()));
assertParseTypeAndConvert("function(string, ...number): number")
.isEqualTo(new TypeDeclarationNode(FUNCTION_TYPE, numberType(), p1, p2));
}
public void testConvertOptionalFunctionParameters() throws Exception {
LinkedHashMap<String, TypeDeclarationNode> requiredParams = new LinkedHashMap<>();
LinkedHashMap<String, TypeDeclarationNode> optionalParams = new LinkedHashMap<>();
optionalParams.put("p1", stringType());
optionalParams.put("p2", numberType());
assertParseTypeAndConvert("function(?string=, number=)")
.isEqualTo(functionType(anyType(), requiredParams, optionalParams, null, null));
}
private NodeSubject assertParseTypeAndConvert(final String typeExpr) {
Node oldAST = JsDocInfoParser.parseTypeString(typeExpr);
assertNotNull(typeExpr + " did not produce a parsed AST", oldAST);
return new NodeSubject(THROW_ASSERTION_ERROR,
TypeDeclarationsIRFactory.convertTypeNodeAST(oldAST));
}
}