
com.google.javascript.jscomp.PolymerBehaviorExtractor Maven / Gradle / Ivy
/*
* 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.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.GlobalNamespace.Name;
import com.google.javascript.jscomp.GlobalNamespace.Ref;
import com.google.javascript.jscomp.PolymerPass.MemberDefinition;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.List;
import javax.annotation.Nullable;
/**
* Finds the Polymer behavior definitions associated with Polymer element definitions.
* @see https://www.polymer-project.org/1.0/docs/devguide/behaviors
*/
final class PolymerBehaviorExtractor {
private static final ImmutableSet BEHAVIOR_NAMES_NOT_TO_COPY = ImmutableSet.of(
"created", "attached", "detached", "attributeChanged", "configure", "ready",
"properties", "listeners", "observers", "hostAttributes");
private final AbstractCompiler compiler;
private final GlobalNamespace globalNames;
PolymerBehaviorExtractor(AbstractCompiler compiler, GlobalNamespace globalNames) {
this.compiler = compiler;
this.globalNames = globalNames;
}
/**
* Extracts all Behaviors from an array literal, recursively. Entries in the array can be
* object literals or array literals (of other behaviors). Behavior names must be
* global, fully qualified names.
* @see https://github.com/Polymer/polymer/blob/0.8-preview/PRIMER.md#behaviors
* @return A list of all {@code BehaviorDefinitions} in the array.
*/
ImmutableList extractBehaviors(Node behaviorArray) {
if (behaviorArray == null) {
return ImmutableList.of();
}
if (!behaviorArray.isArrayLit()) {
compiler.report(
JSError.make(behaviorArray, PolymerPassErrors.POLYMER_INVALID_BEHAVIOR_ARRAY));
return ImmutableList.of();
}
ImmutableList.Builder behaviors = ImmutableList.builder();
for (Node behaviorName : behaviorArray.children()) {
if (behaviorName.isObjectLit()) {
PolymerPassStaticUtils.switchDollarSignPropsToBrackets(behaviorName, compiler);
PolymerPassStaticUtils.quoteListenerAndHostAttributeKeys(behaviorName, compiler);
if (NodeUtil.getFirstPropMatchingKey(behaviorName, "is") != null) {
compiler.report(JSError.make(behaviorName, PolymerPassErrors.POLYMER_INVALID_BEHAVIOR));
}
behaviors.add(
new BehaviorDefinition(
PolymerPassStaticUtils.extractProperties(
behaviorName,
PolymerClassDefinition.DefinitionType.ObjectLiteral,
compiler,
/** constructor= */
null),
getBehaviorFunctionsToCopy(behaviorName),
getNonPropertyMembersToCopy(behaviorName),
!NodeUtil.isInFunction(behaviorName),
(FeatureSet) NodeUtil.getEnclosingScript(behaviorName).getProp(Node.FEATURE_SET)));
continue;
}
ResolveBehaviorNameResult resolveResult = resolveBehaviorName(behaviorName);
if (resolveResult == null) {
compiler.report(JSError.make(behaviorName, PolymerPassErrors.POLYMER_UNQUALIFIED_BEHAVIOR));
continue;
}
Node behaviorValue = resolveResult.node;
if (behaviorValue.isArrayLit()) {
// Individual behaviors can also be arrays of behaviors. Parse them recursively.
behaviors.addAll(extractBehaviors(behaviorValue));
} else if (behaviorValue.isObjectLit()) {
PolymerPassStaticUtils.switchDollarSignPropsToBrackets(behaviorValue, compiler);
PolymerPassStaticUtils.quoteListenerAndHostAttributeKeys(behaviorValue, compiler);
if (NodeUtil.getFirstPropMatchingKey(behaviorValue, "is") != null) {
compiler.report(JSError.make(behaviorValue, PolymerPassErrors.POLYMER_INVALID_BEHAVIOR));
}
behaviors.add(
new BehaviorDefinition(
PolymerPassStaticUtils.extractProperties(
behaviorValue,
PolymerClassDefinition.DefinitionType.ObjectLiteral,
compiler,
/** constructor= */
null),
getBehaviorFunctionsToCopy(behaviorValue),
getNonPropertyMembersToCopy(behaviorValue),
resolveResult.isGlobalDeclaration,
(FeatureSet) NodeUtil.getEnclosingScript(behaviorValue).getProp(Node.FEATURE_SET)));
} else {
compiler.report(JSError.make(behaviorName, PolymerPassErrors.POLYMER_UNQUALIFIED_BEHAVIOR));
}
}
return behaviors.build();
}
private static class ResolveBehaviorNameResult {
final Node node;
final boolean isGlobalDeclaration;
public ResolveBehaviorNameResult(Node node, boolean isGlobalDeclaration) {
this.node = node;
this.isGlobalDeclaration = isGlobalDeclaration;
}
}
/**
* Resolve an identifier, which is presumed to refer to a Polymer Behavior declaration, using the
* global namespace. Recurses to resolve assignment chains of any length.
*
* @param nameNode The NAME, GETPROP, or CAST node containing the identifier.
* @return The behavior declaration node, or null if it couldn't be resolved.
*/
@Nullable
private ResolveBehaviorNameResult resolveBehaviorName(Node nameNode) {
String name = getQualifiedNameThroughCast(nameNode);
if (name == null) {
return null;
}
Name globalName = globalNames.getSlot(name);
if (globalName == null) {
return null;
}
boolean isGlobalDeclaration = true;
// Use any set as a backup declaration, even if it's local.
Ref declarationRef = globalName.getDeclaration();
if (declarationRef == null) {
for (Ref ref : globalName.getRefs()) {
if (ref.isSet()) {
isGlobalDeclaration = false;
declarationRef = ref;
break;
}
}
}
if (declarationRef == null) {
return null;
}
Node declarationNode = declarationRef.getNode();
if (declarationNode == null) {
return null;
}
Node rValue = NodeUtil.getRValueOfLValue(declarationNode);
if (rValue == null) {
return null;
}
if (rValue.isQualifiedName()) {
// Another identifier; recurse.
return resolveBehaviorName(rValue);
}
JSDocInfo behaviorInfo = NodeUtil.getBestJSDocInfo(declarationNode);
if (behaviorInfo == null || !behaviorInfo.isPolymerBehavior()) {
compiler.report(
JSError.make(declarationNode, PolymerPassErrors.POLYMER_UNANNOTATED_BEHAVIOR));
}
return new ResolveBehaviorNameResult(rValue, isGlobalDeclaration);
}
/**
* @return A list of functions from a behavior which should be copied to the element prototype.
*/
private static ImmutableList getBehaviorFunctionsToCopy(Node behaviorObjLit) {
checkState(behaviorObjLit.isObjectLit());
ImmutableList.Builder functionsToCopy = ImmutableList.builder();
for (Node keyNode : behaviorObjLit.children()) {
boolean isFunctionDefinition = (keyNode.isStringKey() && keyNode.getFirstChild().isFunction())
|| keyNode.isMemberFunctionDef();
if (isFunctionDefinition && !BEHAVIOR_NAMES_NOT_TO_COPY.contains(keyNode.getString())) {
functionsToCopy.add(new MemberDefinition(NodeUtil.getBestJSDocInfo(keyNode), keyNode,
keyNode.getFirstChild()));
}
}
return functionsToCopy.build();
}
/**
* Similar to {@link Node#getQualifiedName} but also handles CAST nodes. For example, given a
* GETPROP representing "(/** @type {?} *\/ (x)).y.z" returns "x.y.z". Returns null if node is
* not a NAME, GETPROP, or CAST. See b/64389806 for Polymer-specific context.
*/
@Nullable
private static String getQualifiedNameThroughCast(Node node) {
if (node.isName()) {
String name = node.getString();
return name.isEmpty() ? null : name;
} else if (node.isGetProp()) {
String left = getQualifiedNameThroughCast(node.getFirstChild());
if (left == null) {
return null;
}
String right = node.getLastChild().getString();
return left + "." + right;
} else if (node.isCast()) {
return getQualifiedNameThroughCast(node.getFirstChild());
}
return null;
}
/**
* @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 getNonPropertyMembersToCopy(Node behaviorObjLit) {
checkState(behaviorObjLit.isObjectLit());
ImmutableList.Builder membersToCopy = ImmutableList.builder();
for (Node keyNode : behaviorObjLit.children()) {
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();
}
/**
* Parsed definition of a Polymer Behavior. Includes members which should be copied to elements
* which use the behavior.
*/
static final class BehaviorDefinition {
/**
* Properties declared in the behavior 'properties' block.
*/
final List props;
/**
* Functions intended to be copied to elements which use this Behavior.
*/
final List functionsToCopy;
/**
* Other members intended to be copied to elements which use this Behavior.
*/
final List nonPropertyMembersToCopy;
/**
* Whether this Behavior is declared in the global scope.
*/
final boolean isGlobalDeclaration;
/**
* Language features to carry over to the extraction destination.
*/
final FeatureSet features;
BehaviorDefinition(
List props, List functionsToCopy,
List nonPropertyMembersToCopy, boolean isGlobalDeclaration,
FeatureSet features) {
this.props = props;
this.functionsToCopy = functionsToCopy;
this.nonPropertyMembersToCopy = nonPropertyMembersToCopy;
this.isGlobalDeclaration = isGlobalDeclaration;
this.features = features;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy