Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSType.Nullability;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* A code generator that outputs type annotations for functions and
* constructors.
*/
class TypedCodeGenerator extends CodeGenerator {
private final JSTypeRegistry registry;
private final JSDocInfoPrinter jsDocInfoPrinter;
TypedCodeGenerator(
CodeConsumer consumer, CompilerOptions options, JSTypeRegistry registry) {
super(consumer, options);
checkNotNull(registry);
this.registry = registry;
this.jsDocInfoPrinter = new JSDocInfoPrinter(options.getUseOriginalNamesInOutput());
}
@Override
protected void add(Node n, Context context) {
maybeAddTypeAnnotation(n);
super.add(n, context);
}
private void maybeAddTypeAnnotation(Node n) {
Node parent = n.getParent();
if (parent == null) {
// root node cannot have a type annotation.
return;
}
// Generate type annotations only for statements and class member functions.
if (parent.isBlock() || parent.isScript() || parent.isClassMembers()) {
if (n.isClass() || n.isFunction() || n.isMemberFunctionDef()) {
add(getTypeAnnotation(n));
} else if (n.isExprResult()
&& n.getFirstChild().isAssign()) {
Node assign = n.getFirstChild();
if (NodeUtil.isNamespaceDecl(assign.getFirstChild())) {
add(jsDocInfoPrinter.print(assign.getJSDocInfo()));
} else {
Node rhs = assign.getLastChild();
add(getTypeAnnotation(rhs));
}
} else if (NodeUtil.isNameDeclaration(n) && n.getFirstFirstChild() != null) {
if (NodeUtil.isNamespaceDecl(n.getFirstChild())) {
add(jsDocInfoPrinter.print(n.getJSDocInfo()));
} else {
add(getTypeAnnotation(n.getFirstFirstChild()));
}
}
}
}
private String getTypeAnnotation(Node node) {
if (node.isMemberFunctionDef()) {
// For a member function the type information is actually on the function it contains,
// so just generate the type annotation for that.
return getMemberFunctionAnnotation(node.getOnlyChild());
} else if (node.isClass()) {
return getClassAnnotation(node.getJSType());
} else if (node.isFunction()) {
return getFunctionAnnotation(node);
} else {
boolean nodeOriginallyHadJSDoc = NodeUtil.getBestJSDocInfo(node) != null;
if (!nodeOriginallyHadJSDoc) {
// For nodes that don't inherently define a type, ony generate JSDoc if they originally
// had some.
return "";
}
JSType type = node.getJSType();
if (type == null) {
return "";
} else if (type.isFunctionType()) {
return getFunctionAnnotation(node);
} else if (type.isEnumType()) {
return "/** @enum {"
+ type.toMaybeObjectType()
.getEnumeratedTypeOfEnumObject()
.toAnnotationString(Nullability.EXPLICIT)
+ "} */\n";
} else if (!type.isUnknownType()
&& !type.isEmptyType()
&& !type.isVoidType()
&& !type.isFunctionPrototypeType()) {
return "/** @type {" + node.getJSType().toAnnotationString(Nullability.EXPLICIT) + "} */\n";
} else {
return "";
}
}
}
/**
* @param fnNode A node for a function for which to generate a type annotation
*/
private String getFunctionAnnotation(Node fnNode) {
JSType type = fnNode.getJSType();
checkState(fnNode.isFunction() || type.isFunctionType());
if (type == null || type.isUnknownType()) {
return "";
}
FunctionType funType = type.toMaybeFunctionType();
if (type.equals(registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE))) {
return "/** @type {!Function} */\n";
}
StringBuilder sb = new StringBuilder("/**\n");
Node paramNode = null;
// We need to use the child nodes of the function as the nodes for the
// parameters of the function type do not have the real parameter names.
// FUNCTION
// NAME
// PARAM_LIST
// NAME param1
// NAME param2
if (fnNode != null && fnNode.isFunction()) {
paramNode = NodeUtil.getFunctionParameters(fnNode).getFirstChild();
}
// Param types
appendFunctionParamAnnotations(sb, funType, paramNode);
// Return type
JSType retType = funType.getReturnType();
if (retType != null
&& !retType.isEmptyType() // There is no annotation for the empty type.
&& !funType.isInterface() // Interfaces never return a value.
&& !(funType.isConstructor() && retType.isVoidType())) {
sb.append(" * ");
appendAnnotation(sb, "return", retType.toAnnotationString(Nullability.EXPLICIT));
sb.append("\n");
}
// This function could be defining an ES5-style class or interface.
// If it isn't but still requires a type for `this`, then we need to explicitly add
// an annotation for that.
if (funType.isConstructor()) {
// This function is defining an ES5-style class, so include the class annotations here.
appendClassAnnotations(sb, funType);
sb.append(" * @constructor\n");
} else if (funType.isInterface()) {
appendInterfaceAnnotations(sb, funType);
} else {
JSType thisType = funType.getTypeOfThis();
if (thisType != null && !thisType.isUnknownType() && !thisType.isVoidType()) {
if (fnNode == null || !thisType.equals(findMethodOwner(fnNode))) {
sb.append(" * ");
appendAnnotation(sb, "this", thisType.toAnnotationString(Nullability.EXPLICIT));
sb.append("\n");
}
}
}
appendTemplateAnnotations(sb, funType.getTypeParameters());
sb.append(" */\n");
return sb.toString();
}
/** @param fnNode A function node child of a MEMBER_FUNCTION_DEF */
private String getMemberFunctionAnnotation(Node fnNode) {
checkState(fnNode.isFunction() && fnNode.getParent().isMemberFunctionDef(), fnNode);
JSType type = fnNode.getJSType();
if (type == null || type.isUnknownType()) {
return "";
}
FunctionType funType = type.toMaybeFunctionType();
StringBuilder sb = new StringBuilder("/**\n");
// We need to use the child nodes of the function as the nodes for the
// parameters of the function type do not have the real parameter names.
// FUNCTION
// NAME
// PARAM_LIST
// NAME param1
// NAME param2
Node paramNode = NodeUtil.getFunctionParameters(fnNode).getFirstChild();
// Param types
appendFunctionParamAnnotations(sb, funType, paramNode);
if (NodeUtil.isEs6Constructor(fnNode)) {
appendTemplateAnnotations(sb, funType.getConstructorOnlyTemplateParameters());
// no return type for the constructor
} else {
appendTemplateAnnotations(sb, funType.getTypeParameters());
// Return type
JSType retType = funType.getReturnType();
if (retType != null && !retType.isEmptyType()) {
// There is no annotation for the empty type.
sb.append(" * ");
appendAnnotation(sb, "return", retType.toAnnotationString(Nullability.EXPLICIT));
sb.append("\n");
}
}
sb.append(" */\n");
return sb.toString();
}
/**
* Generates @param annotations.
*
* @param sb annotations will be appended here
* @param funType function type
* @param paramNode parameter names will be taken from here
*/
private void appendFunctionParamAnnotations(
StringBuilder sb, FunctionType funType, Node paramNode) {
int minArity = funType.getMinArity();
int maxArity = funType.getMaxArity();
List formals = ImmutableList.copyOf(funType.getParameterTypes());
for (int i = 0; i < formals.size(); i++) {
sb.append(" * ");
appendAnnotation(sb, "param", getParameterJSDocType(formals, i, minArity, maxArity));
String parameterName = getParameterJSDocName(paramNode, i);
sb.append(" ").append(parameterName).append("\n");
if (paramNode != null) {
paramNode = paramNode.getNext();
}
}
}
private String getClassAnnotation(JSType classType) {
checkState(classType.isFunctionType(), classType);
if (classType == null || classType.isUnknownType()) {
return "";
}
FunctionType funType = classType.toMaybeFunctionType();
StringBuilder sb = new StringBuilder();
if (funType.isInterface()) {
appendInterfaceAnnotations(sb, funType);
} else {
checkState(funType.isConstructor(), funType);
appendClassAnnotations(sb, funType);
}
appendTemplateAnnotations(sb, funType.getTypeParameters());
String jsdocContent = sb.toString();
// For simple class, it's possible we didn't end up generating any JSDoc at all.
if (jsdocContent.isEmpty()) {
return jsdocContent;
} else {
return "/**\n" + jsdocContent + " */\n";
}
}
private void appendTemplateAnnotations(
StringBuilder sb, Collection typeParams) {
if (!typeParams.isEmpty()) {
sb.append(" * @template ");
Joiner.on(",").appendTo(sb, Iterables.transform(typeParams, var -> formatTypeVar(var)));
sb.append("\n");
}
}
/**
* Return the name of the parameter to be used in JSDoc, generating one for destructuring
* parameters.
*
* @param paramNode child node of a parameter list
* @param paramIndex position of child in the list
* @return name to use in JSDoc
*/
private String getParameterJSDocName(Node paramNode, int paramIndex) {
Node nameNode = null;
if (paramNode != null) {
checkArgument(paramNode.getParent().isParamList(), paramNode);
if (paramNode.isRest()) {
// use `restParam` of `...restParam`
// restParam might still be a destructuring pattern
paramNode = paramNode.getOnlyChild();
} else if (paramNode.isDefaultValue()) {
// use `defaultParam` of `defaultParam = something`
// defaultParam might still be a destructuring pattern
paramNode = paramNode.getFirstChild();
}
if (paramNode.isName()) {
nameNode = paramNode;
} else {
checkState(paramNode.isObjectPattern() || paramNode.isArrayPattern(), paramNode);
nameNode = null; // must generate a fake name
}
}
if (nameNode == null) {
return "p" + paramIndex;
} else {
checkState(nameNode.isName(), nameNode);
return nameNode.getString();
}
}
private String formatTypeVar(JSType var) {
return var.toAnnotationString(Nullability.IMPLICIT);
}
// TODO(dimvar): it's awkward that we print @constructor after the extends/implements;
// we should print it first, like users write it. Same for @interface and @record.
private void appendClassAnnotations(StringBuilder sb, FunctionType funType) {
FunctionType superConstructor = funType.getInstanceType().getSuperClassConstructor();
if (superConstructor != null) {
ObjectType superInstance = superConstructor.getInstanceType();
if (!superInstance.toString().equals("Object")) {
sb.append(" * ");
appendAnnotation(sb, "extends", superInstance.toAnnotationString(Nullability.IMPLICIT));
sb.append("\n");
}
}
// Avoid duplicates, add implemented type to a set first
Set interfaces = new TreeSet<>();
for (ObjectType interfaze : funType.getAncestorInterfaces()) {
interfaces.add(interfaze.toAnnotationString(Nullability.IMPLICIT));
}
for (String interfaze : interfaces) {
sb.append(" * ");
appendAnnotation(sb, "implements", interfaze);
sb.append("\n");
}
}
private void appendInterfaceAnnotations(StringBuilder sb, FunctionType funType) {
Set interfaces = new TreeSet<>();
for (ObjectType interfaceType : funType.getAncestorInterfaces()) {
interfaces.add(interfaceType.toAnnotationString(Nullability.IMPLICIT));
}
for (String interfaze : interfaces) {
sb.append(" * ");
appendAnnotation(sb, "extends", interfaze);
sb.append("\n");
}
if (funType.isStructuralInterface()) {
sb.append(" * @record\n");
} else {
sb.append(" * @interface\n");
}
}
// TODO(sdh): This whole method could be deleted if we don't mind adding
// additional @this annotations where they're not actually necessary.
/**
* Given a method definition node, returns the {@link ObjectType} corresponding
* to the class the method is defined on, or null if it is not a prototype method.
*/
private ObjectType findMethodOwner(Node n) {
if (n == null) {
return null;
}
Node parent = n.getParent();
FunctionType ctor = null;
if (parent.isAssign()) {
Node target = parent.getFirstChild();
if (NodeUtil.isPrototypeProperty(target)) {
// TODO(johnlenz): handle non-global types
JSType type = registry.getGlobalType(target.getFirstFirstChild().getQualifiedName());
ctor = type != null ? ((ObjectType) type).getConstructor() : null;
}
} else if (parent.isClass()) {
// TODO(sdh): test this case once the type checker understands ES6 classes
ctor = parent.getJSType().toMaybeFunctionType();
}
return ctor != null ? ctor.getInstanceType() : null;
}
private static void appendAnnotation(StringBuilder sb, String name, String type) {
sb.append("@").append(name).append(" {").append(type).append("}");
}
/** Creates a JSDoc-suitable String representation of the type of a parameter. */
private String getParameterJSDocType(List types, int index, int minArgs, int maxArgs) {
JSType type = types.get(index);
if (index < minArgs) {
return type.toAnnotationString(Nullability.EXPLICIT);
}
boolean isRestArgument = maxArgs == Integer.MAX_VALUE && index == types.size() - 1;
if (isRestArgument) {
return "..." + restrictByUndefined(type).toAnnotationString(Nullability.EXPLICIT);
}
return restrictByUndefined(type).toAnnotationString(Nullability.EXPLICIT) + "=";
}
/** Removes undefined from a union type. */
private JSType restrictByUndefined(JSType type) {
// If not voidable, there's nothing to do. If not nullable then the easiest
// thing is to simply remove both null and undefined. If nullable, then add
// null back into the union after removing null and undefined.
if (!type.isVoidable()) {
return type;
}
JSType restricted = type.restrictByNotNullOrUndefined();
if (type.isNullable()) {
JSType nullType = registry.getNativeType(JSTypeNative.NULL_TYPE);
return registry.createUnionType(ImmutableList.of(restricted, nullType));
}
// The bottom type cannot appear in a jsdoc
return restricted.isEmptyType() ? type : restricted;
}
}