com.google.javascript.jscomp.PolymerClassDefinition Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.frontend.js.minifier
Show all versions of com.liferay.frontend.js.minifier
Liferay Frontend JS Minifier
/*
* Copyright 2016 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.checkState;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.PolymerBehaviorExtractor.BehaviorDefinition;
import com.google.javascript.jscomp.PolymerPass.MemberDefinition;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
/**
* Parsed Polymer class (element) definition. Includes convenient fields for rewriting the
* class.
*/
final class PolymerClassDefinition {
static enum DefinitionType {
ObjectLiteral,
ES6Class
}
/** The declaration style used for the Polymer definition */
final DefinitionType defType;
/** The Polymer call or class node which defines the Element. */
final Node definition;
/** The target node (LHS) for the Polymer element definition. */
final Node target;
/** The object literal passed to the call to the Polymer() function. */
final Node descriptor;
/** The constructor function for the element. */
final MemberDefinition constructor;
/** The name of the native HTML element which this element extends. */
@Nullable final String nativeBaseElement;
/** Properties declared in the Polymer "properties" block. */
final List props;
/** Flattened list of behavior definitions used by this element. */
@Nullable final ImmutableList behaviors;
/** Language features that should be carried over to the extraction destination. */
@Nullable final FeatureSet features;
PolymerClassDefinition(
DefinitionType defType,
Node definition,
Node target,
Node descriptor,
JSDocInfo classInfo,
MemberDefinition constructor,
String nativeBaseElement,
List props,
ImmutableList behaviors,
FeatureSet features) {
this.defType = defType;
this.definition = definition;
this.target = target;
checkState(descriptor == null || descriptor.isObjectLit());
this.descriptor = descriptor;
this.constructor = constructor;
this.nativeBaseElement = nativeBaseElement;
this.props = props;
this.behaviors = behaviors;
this.features = features;
}
/**
* Validates the class definition and if valid, destructively extracts the class definition from
* the AST.
*/
@Nullable static PolymerClassDefinition extractFromCallNode(
Node callNode, AbstractCompiler compiler, GlobalNamespace globalNames) {
Node descriptor = NodeUtil.getArgumentForCallOrNew(callNode, 0);
if (descriptor == null || !descriptor.isObjectLit()) {
// report bad class definition
compiler.report(JSError.make(callNode, PolymerPassErrors.POLYMER_DESCRIPTOR_NOT_VALID));
return null;
}
int paramCount = callNode.getChildCount() - 1;
if (paramCount != 1) {
compiler.report(JSError.make(callNode, PolymerPassErrors.POLYMER_UNEXPECTED_PARAMS));
return null;
}
Node elName = NodeUtil.getFirstPropMatchingKey(descriptor, "is");
if (elName == null) {
compiler.report(JSError.make(callNode, PolymerPassErrors.POLYMER_MISSING_IS));
return null;
}
Node target;
if (NodeUtil.isNameDeclaration(callNode.getGrandparent())) {
target = IR.name(callNode.getParent().getString());
} else if (callNode.getParent().isAssign()) {
target = callNode.getParent().getFirstChild().cloneTree();
} else {
String elNameStringBase =
elName.isQualifiedName()
? elName.getQualifiedName().replace('.', '$')
: elName.getString();
String elNameString = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, elNameStringBase);
elNameString += "Element";
target = IR.name(elNameString);
}
JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(target);
JSDocInfo ctorInfo = null;
Node constructor = NodeUtil.getFirstPropMatchingKey(descriptor, "factoryImpl");
if (constructor == null) {
constructor = NodeUtil.emptyFunction();
compiler.reportChangeToChangeScope(constructor);
constructor.useSourceInfoFromForTree(callNode);
} else {
ctorInfo = NodeUtil.getBestJSDocInfo(constructor);
}
Node baseClass = NodeUtil.getFirstPropMatchingKey(descriptor, "extends");
String nativeBaseElement = baseClass == null ? null : baseClass.getString();
Node behaviorArray = NodeUtil.getFirstPropMatchingKey(descriptor, "behaviors");
PolymerBehaviorExtractor behaviorExtractor =
new PolymerBehaviorExtractor(compiler, globalNames);
ImmutableList behaviors = behaviorExtractor.extractBehaviors(behaviorArray);
List allProperties = new ArrayList<>();
for (BehaviorDefinition behavior : behaviors) {
overwriteMembersIfPresent(allProperties, behavior.props);
}
overwriteMembersIfPresent(
allProperties,
PolymerPassStaticUtils.extractProperties(
descriptor, DefinitionType.ObjectLiteral, compiler));
FeatureSet newFeatures = null;
if (!behaviors.isEmpty()) {
newFeatures = behaviors.get(0).features;
for (int i = 1; i < behaviors.size(); i++) {
newFeatures = newFeatures.union(behaviors.get(i).features);
}
}
return new PolymerClassDefinition(
DefinitionType.ObjectLiteral,
callNode,
target,
descriptor,
classInfo,
new MemberDefinition(ctorInfo, null, constructor),
nativeBaseElement,
allProperties,
behaviors,
newFeatures);
}
/**
* Validates the class definition and if valid, extracts the class definition from the AST. As
* opposed to the Polymer 1 extraction, this operation is non-destructive.
*/
@Nullable
static PolymerClassDefinition extractFromClassNode(
Node classNode, AbstractCompiler compiler, GlobalNamespace globalNames) {
checkState(classNode != null && classNode.isClass());
// The supported case is for the config getter to return an object literal descriptor.
Node propertiesDescriptor = null;
Node propertiesGetter =
NodeUtil.getFirstGetterMatchingKey(NodeUtil.getClassMembers(classNode), "properties");
if (propertiesGetter != null) {
if (!propertiesGetter.isStaticMember()) {
// report bad class definition
compiler.report(
JSError.make(classNode, PolymerPassErrors.POLYMER_CLASS_PROPERTIES_NOT_STATIC));
} else {
for (Node child : NodeUtil.getFunctionBody(propertiesGetter.getFirstChild()).children()) {
if (child.isReturn()) {
if (child.hasChildren() && child.getFirstChild().isObjectLit()) {
propertiesDescriptor = child.getFirstChild();
break;
} else {
compiler.report(
JSError.make(
propertiesGetter, PolymerPassErrors.POLYMER_CLASS_PROPERTIES_INVALID));
}
}
}
}
}
Node target;
if (NodeUtil.isNameDeclaration(classNode.getGrandparent())) {
target = IR.name(classNode.getParent().getString());
} else if (classNode.getParent().isAssign()
&& classNode.getParent().getFirstChild().isQualifiedName()) {
target = classNode.getParent().getFirstChild();
} else if (!classNode.getFirstChild().isEmpty()) {
target = classNode.getFirstChild();
} else {
// issue error - no name found
compiler.report(JSError.make(classNode, PolymerPassErrors.POLYMER_CLASS_UNNAMED));
return null;
}
JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(classNode);
JSDocInfo ctorInfo = null;
Node constructor =
NodeUtil.getFirstPropMatchingKey(NodeUtil.getClassMembers(classNode), "constructor");
if (constructor != null) {
ctorInfo = NodeUtil.getBestJSDocInfo(constructor);
}
List allProperties =
PolymerPassStaticUtils.extractProperties(
propertiesDescriptor, DefinitionType.ES6Class, compiler);
return new PolymerClassDefinition(
DefinitionType.ES6Class,
classNode,
target,
propertiesDescriptor,
classInfo,
new MemberDefinition(ctorInfo, null, constructor),
null,
allProperties,
null,
null);
}
/**
* Appends a list of new MemberDefinitions to the end of a list and removes any previous
* MemberDefinition in the list which has the same name as the new member.
*/
private static void overwriteMembersIfPresent(
List list, List newMembers) {
for (MemberDefinition newMember : newMembers) {
for (MemberDefinition member : list) {
if (member.name.getString().equals(newMember.name.getString())) {
list.remove(member);
break;
}
}
list.add(newMember);
}
}
}