use of com.google.javascript.jscomp.PolymerPass.MemberDefinition 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.jscomp.PolymerPass.MemberDefinition 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.jscomp.PolymerPass.MemberDefinition 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.PolymerPass.MemberDefinition in project closure-compiler by google.
the class PolymerPassSuppressBehaviors method suppressDefaultValues.
private void suppressDefaultValues(Node behaviorValue) {
for (MemberDefinition property : PolymerPassStaticUtils.extractProperties(behaviorValue, PolymerClassDefinition.DefinitionType.ObjectLiteral, compiler, /**
* constructor=
*/
null)) {
if (!property.value.isObjectLit()) {
continue;
}
Node defaultValue = NodeUtil.getFirstPropMatchingKey(property.value, "value");
if (defaultValue == null || !defaultValue.isFunction()) {
continue;
}
Node defaultValueKey = defaultValue.getParent();
JSDocInfo.Builder suppressDoc = JSDocInfo.Builder.maybeCopyFrom(defaultValueKey.getJSDocInfo());
suppressDoc.recordSuppressions(ImmutableSet.of("checkTypes", "globalThis", "visibility"));
defaultValueKey.setJSDocInfo(suppressDoc.build());
}
}
use of com.google.javascript.jscomp.PolymerPass.MemberDefinition in project closure-compiler by google.
the class PolymerBehaviorExtractor method getNonPropertyMembersToCopy.
/**
* @return A list of MemberDefinitions in a behavior which are not in the properties block, but
* should still be copied to the element prototype.
*/
private static ImmutableList<MemberDefinition> getNonPropertyMembersToCopy(Node behaviorObjLit) {
checkState(behaviorObjLit.isObjectLit());
ImmutableList.Builder<MemberDefinition> membersToCopy = ImmutableList.builder();
for (Node keyNode = behaviorObjLit.getFirstChild(); keyNode != null; keyNode = keyNode.getNext()) {
boolean isNonFunctionMember = keyNode.isGetterDef() || (keyNode.isStringKey() && !keyNode.getFirstChild().isFunction());
if (isNonFunctionMember && !BEHAVIOR_NAMES_NOT_TO_COPY.contains(keyNode.getString())) {
membersToCopy.add(new MemberDefinition(NodeUtil.getBestJSDocInfo(keyNode), keyNode, keyNode.getFirstChild()));
}
}
return membersToCopy.build();
}
Aggregations