use of com.google.javascript.rhino.JSTypeExpression in project closure-compiler by google.
the class PolymerClassRewriter method appendPropertiesToBlock.
/**
* Appends all of the given properties to the given block.
*/
private void appendPropertiesToBlock(List<MemberDefinition> props, Node block, String basePath, boolean isExternsBlock) {
for (MemberDefinition prop : props) {
Node propertyNode = getPropertyNode(prop, basePath);
if (propertyNode == null) {
continue;
}
JSTypeExpression propType = PolymerPassStaticUtils.getTypeFromProperty(prop, compiler);
if (propType == null) {
continue;
}
JSDocInfo info = null;
if (isExternsBlock) {
info = replaceJSDocAndAddNewVars(prop, propType, block);
} else {
JSDocInfo.Builder infoBuilder = JSDocInfo.Builder.maybeCopyFrom(prop.info);
infoBuilder.recordType(propType);
info = infoBuilder.build();
}
propertyNode.getFirstChild().setJSDocInfo(info);
block.addChildToBack(propertyNode);
}
}
use of com.google.javascript.rhino.JSTypeExpression in project closure-compiler by google.
the class PolymerClassRewriter method rewritePolymerClassDeclaration.
/**
* Rewrites a class which extends Polymer.Element to a set of declarations and assignments which
* can be understood by the compiler.
*
* @param clazz The class node
* @param cls The extracted {@link PolymerClassDefinition} for the Polymer element created by this
* call.
*/
void rewritePolymerClassDeclaration(Node clazz, NodeTraversal traversal, final PolymerClassDefinition cls) {
if (cls.descriptor != null) {
addTypesToFunctions(cls.descriptor, cls.target.getQualifiedName(), cls.defType);
}
PolymerPassStaticUtils.switchDollarSignPropsToBrackets(NodeUtil.getClassMembers(clazz), compiler);
for (MemberDefinition prop : cls.props) {
if (prop.value.isObjectLit()) {
PolymerPassStaticUtils.switchDollarSignPropsToBrackets(prop.value, compiler);
}
}
// For simplicity add everything into a block, before adding it to the AST.
Node block = IR.block();
appendBehaviorPropertiesToBlock(cls, block, cls.target.getQualifiedName() + ".prototype.", /*isExternsBlock*/
false);
// For each Polymer property we found in the "properties" configuration object, append a
// property declaration to the prototype (e.g. "/** @type {string} */ MyElement.prototype.foo").
appendPropertiesToBlock(cls.props, block, cls.target.getQualifiedName() + ".prototype.", /* isExternsBlock= */
false);
ImmutableList<MemberDefinition> readOnlyProps = parseReadOnlyProperties(cls, block);
ImmutableList<MemberDefinition> attributeReflectedProps = parseAttributeReflectedProperties(cls);
createExportsAndExterns(cls, readOnlyProps, attributeReflectedProps);
// If an external interface is required, mark the class as implementing it
if (polymerExportPolicy == PolymerExportPolicy.EXPORT_ALL || !readOnlyProps.isEmpty() || !attributeReflectedProps.isEmpty()) {
Node jsDocInfoNode = NodeUtil.getBestJSDocInfoNode(clazz);
JSDocInfo.Builder classInfo = JSDocInfo.Builder.maybeCopyFrom(jsDocInfoNode.getJSDocInfo());
String interfaceName = cls.getInterfaceName(compiler.getUniqueNameIdSupplier());
JSTypeExpression interfaceType = new JSTypeExpression(new Node(Token.BANG, IR.string(interfaceName)).srcrefTree(jsDocInfoNode), VIRTUAL_FILE);
classInfo.recordImplementedInterface(interfaceType);
jsDocInfoNode.setJSDocInfo(classInfo.build());
}
Node insertAfterReference = NodeUtil.getEnclosingStatement(clazz);
if (block.hasChildren()) {
removePropertyDocs(cls.descriptor, cls.defType);
Node newInsertAfterReference = block.getLastChild();
insertAfterReference.getParent().addChildrenAfter(block.removeChildren(), insertAfterReference);
compiler.reportChangeToEnclosingScope(insertAfterReference);
insertAfterReference = newInsertAfterReference;
}
addReturnTypeIfMissing(cls, "is", new JSTypeExpression(IR.string("string"), VIRTUAL_FILE));
Node type = new Node(Token.BANG);
Node array = IR.string("Array");
type.addChildToBack(array);
Node arrayTemplateType = new Node(Token.BLOCK, IR.string("string"));
array.addChildToBack(arrayTemplateType);
addReturnTypeIfMissing(cls, "observers", new JSTypeExpression(type, VIRTUAL_FILE));
addReturnTypeIfMissing(cls, "properties", new JSTypeExpression(IR.string(POLYMER_ELEMENT_PROP_CONFIG), VIRTUAL_FILE));
// and switch simple observers to direct function references.
if (propertyRenamingEnabled && cls.descriptor != null) {
convertSimpleObserverStringsToReferences(cls);
List<Node> propertySinks = new ArrayList<>();
if (polymerExportPolicy != PolymerExportPolicy.EXPORT_ALL) {
propertySinks.addAll(addComputedPropertiesReflectionCalls(cls));
propertySinks.addAll(addComplexObserverReflectionCalls(cls));
}
if (!propertySinks.isEmpty()) {
if (!propertySinkExternInjected && traversal.getScope().getVar(CheckSideEffects.PROTECTOR_FN) == null) {
CheckSideEffects.addExtern(compiler);
propertySinkExternInjected = true;
}
for (Node propertyRef : propertySinks) {
Node name = IR.name(CheckSideEffects.PROTECTOR_FN).srcref(propertyRef);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node protectorCall = IR.call(name, propertyRef).srcref(propertyRef);
protectorCall.putBooleanProp(Node.FREE_CALL, true);
protectorCall = IR.exprResult(protectorCall).srcref(propertyRef);
protectorCall.insertAfter(insertAfterReference);
insertAfterReference = protectorCall;
}
compiler.reportChangeToEnclosingScope(insertAfterReference);
}
addPropertiesConfigObjectReflection(cls, cls.descriptor);
}
}
use of com.google.javascript.rhino.JSTypeExpression in project closure-compiler by google.
the class PolymerClassRewriter method createExportsAndExterns.
/**
* Create exports and externs to protect element properties and methods from renaming and dead
* code removal.
*
* <p>Since Polymer templates, observers, and computed properties rely on string references to
* element properties and methods, and because we don't yet have a way to update those references
* reliably, we instead export or extern them.
*
* <p>For properties, we create a new interface called {@code Polymer<ElementName>Interface}, add
* all element properties to it, mark that the element class {@code @implements} this interface,
* and add the interface to the Closure externs. The specific set of properties we add to this
* interface is determined by the value of {@code polymerExportPolicy}.
*
* <p>For methods, when {@code polymerExportPolicy = EXPORT_ALL}, we instead append to {@code
* Object.prototype} in the externs using {@code @export} annotations. This approach is a
* compromise, with the following alternatives considered:
*
* <p>Alternative 1: Add methods to our generated {@code Polymer<ElementName>Interface} in the
* externs. Pro: More optimal than {@code Object.prototype} when type-aware optimizations are
* enabled. Con 1: When a class {@code @implements} an interface, and when {@code
* report_missing_override} is enabled, any method on the class that is also in the interface must
* have an {@code @override} annotation, which means we generate a spurious warning for all
* methods. Con 2: An unresolved bug was encountered (b/115942961) relating to a mismatch between
* the signatures of the class and the generated interface.
*
* <p>Alternative 2: Generate goog.exportProperty calls, which causes aliases on the prototype
* from original to optimized names to be set. Pro: Compiled code can still use the optimized
* name. Con: In practice, for Polymer applications, we see a net increase in bundle size due to
* the high number of new {@code Foo.prototype.originalName = Foo.prototype.z} expressions.
*
* <p>Alternative 3: Append directly to the {@code Object.prototype} externs, instead of using
* {@code @export} annotations for the {@link GenerateExports} pass. Pro: Doesn't depend on the
* {@code generate_exports} and {@code export_local_property_definitions} flags. Con: The
* PolymerPass runs in the type checking phase, so modifying {@code Object.prototype} here causes
* unwanted type checking effects, such as allowing the method to be called on any object, and
* generating incorrect warnings when {@code report_missing_override} is enabled.
*/
private void createExportsAndExterns(final PolymerClassDefinition cls, List<MemberDefinition> readOnlyProps, List<MemberDefinition> attributeReflectedProps) {
Node block = IR.block();
String interfaceName = cls.getInterfaceName(compiler.getUniqueNameIdSupplier());
Node fnNode = NodeUtil.emptyFunction();
compiler.reportChangeToChangeScope(fnNode);
Node varNode = IR.var(NodeUtil.newQName(compiler, interfaceName), fnNode);
JSDocInfo.Builder info = JSDocInfo.builder().parseDocumentation();
info.recordInterface();
varNode.setJSDocInfo(info.build());
block.addChildToBack(varNode);
String interfaceBasePath = interfaceName + ".prototype.";
if (polymerExportPolicy == PolymerExportPolicy.EXPORT_ALL) {
appendBehaviorPropertiesToBlock(cls, block, interfaceBasePath, /*isExternsBlock*/
true);
appendPropertiesToBlock(cls.props, block, interfaceBasePath, /* isExternsBlock= */
true);
// Methods from behaviors were not already added to our element definition, so we need to
// export those in addition to methods defined directly on the element. Note it's possible
// and valid for two behaviors, or a behavior and an element, to implement the same method,
// so we de-dupe by name. We're not checking that the signatures are compatible in the way
// that normal class inheritance would, but that's not easy to do since these aren't classes.
// Class mixins replace Polymer behaviors and are supported directly by Closure, so new code
// should use those instead.
LinkedHashMap<String, MemberDefinition> uniqueMethods = new LinkedHashMap<>();
if (cls.behaviors != null) {
for (BehaviorDefinition behavior : cls.behaviors) {
for (MemberDefinition method : behavior.functionsToCopy) {
uniqueMethods.put(method.name.getString(), method);
}
}
}
for (MemberDefinition method : cls.methods) {
uniqueMethods.put(method.name.getString(), method);
}
for (MemberDefinition method : uniqueMethods.values()) {
addMethodToObjectExternsUsingExportAnnotation(cls, method);
}
} else if (polymerVersion == 1) {
// For Polymer 1, all declared properties are non-renameable
appendBehaviorPropertiesToBlock(cls, block, interfaceBasePath, /*isExternsBlock*/
true);
appendPropertiesToBlock(cls.props, block, interfaceBasePath, /* isExternsBlock= */
true);
} else {
// For Polymer 2, only read-only properties and reflectToAttribute properties are
// non-renameable. Other properties follow the ALL_UNQUOTED renaming rules.
List<MemberDefinition> interfaceProperties = new ArrayList<>();
interfaceProperties.addAll(readOnlyProps);
if (attributeReflectedProps != null) {
interfaceProperties.addAll(attributeReflectedProps);
}
// Readonly properties and attributeReflected properties for Polymer Element and its behaviors
// are stored together
appendPropertiesToBlock(interfaceProperties, block, interfaceBasePath, /* isExternsBlock= */
true);
}
for (MemberDefinition prop : readOnlyProps) {
// Add all _set* functions to avoid renaming.
String propName = prop.name.getString();
String setterName = "_set" + propName.substring(0, 1).toUpperCase(Locale.ROOT) + propName.substring(1);
Node setterExprNode = IR.exprResult(NodeUtil.newQName(compiler, interfaceBasePath + setterName));
JSDocInfo.Builder setterInfo = JSDocInfo.builder().parseDocumentation();
JSTypeExpression propType = PolymerPassStaticUtils.getTypeFromProperty(prop, compiler);
JSTypeExpression unknown = new JSTypeExpression(new Node(Token.QMARK), propType.getSourceName());
setterInfo.recordParameter(propName, unknown);
setterExprNode.getFirstChild().setJSDocInfo(setterInfo.build());
block.addChildToBack(setterExprNode);
}
block.srcrefTreeIfMissing(externsInsertionRef);
Node stmts = block.removeChildren();
externsInsertionRef.addChildrenToBack(stmts);
compiler.reportChangeToEnclosingScope(stmts);
}
use of com.google.javascript.rhino.JSTypeExpression in project closure-compiler by google.
the class FunctionTypeBuilder method inferInheritance.
/**
* Infer any supertypes from the JSDocInfo or the passed-in base type.
*
* @param info JSDoc info that is attached to the type declaration, if any
* @param classExtendsType The type of the extends clause in `class C extends SuperClass {}`, if
* present.
* @return this object
*/
FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info, @Nullable ObjectType classExtendsType) {
if (info != null && info.hasBaseType()) {
if (isConstructor || isInterface) {
JSType infoBaseType = info.getBaseType().evaluate(templateScope, typeRegistry);
if (!areCompatibleExtendsTypes(infoBaseType.toMaybeObjectType(), classExtendsType)) {
this.isKnownAmbiguous = true;
}
if (infoBaseType.setValidator(new ExtendedTypeValidator())) {
baseType = infoBaseType.toObjectType();
}
} else {
reportWarning(EXTENDS_WITHOUT_TYPEDEF, formatFnName());
this.isKnownAmbiguous = true;
}
} else if (classExtendsType != null && (isConstructor || isInterface)) {
// This case is:
// // no JSDoc here
// class extends astBaseType {...}
//
// It may well be that astBaseType is something dynamically created, like a value passed into
// a function. A common pattern is:
//
// function mixinX(superClass) {
// return class extends superClass {
// ...
// };
// }
// The ExtendedTypeValidator() used in the JSDocInfo case above will report errors for these
// cases, and we don't want that.
// Since astBaseType is an actual value in code rather than an annotation, we can
// rely on validation elsewhere to ensure it is actually defined.
baseType = classExtendsType;
}
// Implemented interfaces (for constructors only).
if (info != null && info.getImplementedInterfaceCount() > 0) {
if (isConstructor) {
implementedInterfaces = new ArrayList<>();
for (JSTypeExpression t : info.getImplementedInterfaces()) {
JSType maybeInterType = t.evaluate(templateScope, typeRegistry);
if (maybeInterType.setValidator(new ImplementedTypeValidator())) {
implementedInterfaces.add((ObjectType) maybeInterType);
}
}
} else if (isInterface) {
reportWarning(TypeCheck.CONFLICTING_IMPLEMENTED_TYPE, formatFnName());
} else {
reportWarning(CONSTRUCTOR_REQUIRED, "@implements", formatFnName());
}
}
// We've already emitted a warning if this is not an interface.
if (isInterface) {
extendedInterfaces = new ArrayList<>();
if (info != null) {
for (JSTypeExpression t : info.getExtendedInterfaces()) {
JSType maybeInterfaceType = t.evaluate(templateScope, typeRegistry);
if (maybeInterfaceType != null) {
// setValidator runs validation and returns whether validation was successful (except
// for not-yet resolved named types, where validation is delayed).
// This code must run even for non-object types (which we know are invalid) to generate
// and record the user error message.
boolean isValid = maybeInterfaceType.setValidator(new ExtendedTypeValidator());
// setValidator might not (e.g. due to delayed execution).
if (isValid && maybeInterfaceType.toMaybeObjectType() != null) {
extendedInterfaces.add(maybeInterfaceType.toMaybeObjectType());
}
}
// de-dupe baseType (from extends keyword) if it's also in @extends jsdoc.
if (classExtendsType != null && maybeInterfaceType.isSubtypeOf(classExtendsType)) {
classExtendsType = null;
}
}
}
if (classExtendsType != null && classExtendsType.setValidator(new ExtendedTypeValidator())) {
// case is:
// /**
// * @interface
// * @extends {OtherInterface}
// */
// class SomeInterface extends astBaseType {}
// Add the explicit extends type to the extended interfaces listed in JSDoc.
extendedInterfaces.add(classExtendsType);
}
}
return this;
}
use of com.google.javascript.rhino.JSTypeExpression in project closure-compiler by google.
the class FunctionTypeBuilder method inferReturnType.
/**
* Infer the return type from JSDocInfo.
* @param fromInlineDoc Indicates whether return type is inferred from inline
* doc attached to function name
*/
FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info, boolean fromInlineDoc) {
if (info != null) {
JSTypeExpression returnTypeExpr = fromInlineDoc ? info.getType() : info.getReturnType();
if (returnTypeExpr != null) {
returnType = returnTypeExpr.evaluate(templateScope, typeRegistry);
returnTypeInferred = false;
}
}
return this;
}
Aggregations