use of com.google.javascript.jscomp.PolymerBehaviorExtractor.BehaviorDefinition 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.jscomp.PolymerBehaviorExtractor.BehaviorDefinition in project closure-compiler by google.
the class PolymerClassDefinition method removeBehaviorPropsOverlappingWithElementProps.
/**
* Removes any behavior properties from the given map that have the same name as a property in the
* given list.
*
* <p>For example, if a Polymer element with a property "name" depends on a behavior with a
* property "name", then this method removes the behavior property in favor of the element
* property.
*/
private static void removeBehaviorPropsOverlappingWithElementProps(Map<MemberDefinition, BehaviorDefinition> behaviorProps, List<MemberDefinition> polymerElementProps) {
if (behaviorProps == null) {
return;
}
Set<String> elementPropNames = polymerElementProps.stream().map(x -> x.name.getString()).collect(Collectors.toSet());
Iterator<Map.Entry<MemberDefinition, BehaviorDefinition>> behaviorsItr = behaviorProps.entrySet().iterator();
while (behaviorsItr.hasNext()) {
MemberDefinition memberDefinition = behaviorsItr.next().getKey();
if (elementPropNames.contains(memberDefinition.name.getString())) {
behaviorsItr.remove();
}
}
}
Aggregations