Skip to content

Commit

Permalink
Refactoring and style cleanups for PolymerPass to make things more ma…
Browse files Browse the repository at this point in the history
…nageable.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=115406177
  • Loading branch information
blickly committed Feb 24, 2016
1 parent b32de63 commit 0e5edda
Show file tree
Hide file tree
Showing 15 changed files with 1,981 additions and 961 deletions.
206 changes: 206 additions & 0 deletions src/com/google/javascript/jscomp/PolymerBehaviorExtractor.java
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,206 @@
/*
* 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 com.google.common.base.Preconditions;
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.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;

import java.util.List;

/**
* Helpers to extract behaviors from Polymer element declarations.
*/
final class PolymerBehaviorExtractor {

private static final ImmutableSet<String> 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<BehaviorDefinition> 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<BehaviorDefinition> behaviors = ImmutableList.builder();
for (Node behaviorName : behaviorArray.children()) {
if (behaviorName.isObjectLit()) {
PolymerPassStaticUtils.switchDollarSignPropsToBrackets(behaviorName, compiler);
PolymerPassStaticUtils.quoteListenerAndHostAttributeKeys(behaviorName);
behaviors.add(new BehaviorDefinition(
PolymerPassStaticUtils.extractProperties(behaviorName),
getBehaviorFunctionsToCopy(behaviorName),
getNonPropertyMembersToCopy(behaviorName),
!NodeUtil.isInFunction(behaviorName)));
continue;
}

Name behaviorGlobalName = globalNames.getSlot(behaviorName.getQualifiedName());
boolean isGlobalDeclaration = true;
if (behaviorGlobalName == null) {
compiler.report(JSError.make(behaviorName, PolymerPassErrors.POLYMER_UNQUALIFIED_BEHAVIOR));
continue;
}

Ref behaviorDeclaration = behaviorGlobalName.getDeclaration();

// Use any set as a backup declaration, even if it's local.
if (behaviorDeclaration == null) {
List<Ref> behaviorRefs = behaviorGlobalName.getRefs();
for (Ref ref : behaviorRefs) {
if (ref.isSet()) {
isGlobalDeclaration = false;
behaviorDeclaration = ref;
break;
}
}
}

if (behaviorDeclaration == null) {
compiler.report(JSError.make(behaviorName, PolymerPassErrors.POLYMER_UNQUALIFIED_BEHAVIOR));
continue;
}

Node behaviorDeclarationNode = behaviorDeclaration.getNode();
JSDocInfo behaviorInfo = NodeUtil.getBestJSDocInfo(behaviorDeclarationNode);
if (behaviorInfo == null || !behaviorInfo.isPolymerBehavior()) {
compiler.report(
JSError.make(behaviorDeclarationNode, PolymerPassErrors.POLYMER_UNANNOTATED_BEHAVIOR));
}

Node behaviorValue = NodeUtil.getRValueOfLValue(behaviorDeclarationNode);

if (behaviorValue == null) {
compiler.report(JSError.make(behaviorName, PolymerPassErrors.POLYMER_UNQUALIFIED_BEHAVIOR));
} else 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);
behaviors.add(new BehaviorDefinition(
PolymerPassStaticUtils.extractProperties(behaviorValue),
getBehaviorFunctionsToCopy(behaviorValue),
getNonPropertyMembersToCopy(behaviorValue),
isGlobalDeclaration));
} else {
compiler.report(JSError.make(behaviorName, PolymerPassErrors.POLYMER_UNQUALIFIED_BEHAVIOR));
}
}

return behaviors.build();
}

/**
* @return A list of functions from a behavior which should be copied to the element prototype.
*/
private static ImmutableList<MemberDefinition> getBehaviorFunctionsToCopy(Node behaviorObjLit) {
Preconditions.checkState(behaviorObjLit.isObjectLit());
ImmutableList.Builder<MemberDefinition> 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();
}

/**
* @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) {
Preconditions.checkState(behaviorObjLit.isObjectLit());
ImmutableList.Builder<MemberDefinition> 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<MemberDefinition> props;

/**
* Functions intended to be copied to elements which use this Behavior.
*/
final List<MemberDefinition> functionsToCopy;

/**
* Other members intended to be copied to elements which use this Behavior.
*/
final List<MemberDefinition> nonPropertyMembersToCopy;

/**
* Whether this Behvaior is declared in the global scope.
*/
final boolean isGlobalDeclaration;

BehaviorDefinition(
List<MemberDefinition> props, List<MemberDefinition> functionsToCopy,
List<MemberDefinition> nonPropertyMembersToCopy, boolean isGlobalDeclaration) {
this.props = props;
this.functionsToCopy = functionsToCopy;
this.nonPropertyMembersToCopy = nonPropertyMembersToCopy;
this.isGlobalDeclaration = isGlobalDeclaration;
}
}
}
161 changes: 161 additions & 0 deletions src/com/google/javascript/jscomp/PolymerClassDefinition.java
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* 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 com.google.common.base.CaseFormat;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.PolymerBehaviorExtractor.BehaviorDefinition;
import com.google.javascript.jscomp.PolymerPass.MemberDefinition;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;

import java.util.LinkedList;
import java.util.List;

import javax.annotation.Nullable;

/**
* Parsed Polymer class (element) definition. Includes convenient fields for rewriting the
* class.
*/
final class PolymerClassDefinition {

/** 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. */
final String nativeBaseElement;

/** Properties declared in the Polymer "properties" block. */
final List<MemberDefinition> props;

/** Flattened list of behavior definitions used by this element. */
final ImmutableList<BehaviorDefinition> behaviors;

PolymerClassDefinition(
Node target,
Node descriptor,
JSDocInfo classInfo,
MemberDefinition constructor,
String nativeBaseElement,
List<MemberDefinition> props,
ImmutableList<BehaviorDefinition> behaviors) {
this.target = target;
Preconditions.checkState(descriptor.isObjectLit());
this.descriptor = descriptor;
this.constructor = constructor;
this.nativeBaseElement = nativeBaseElement;
this.props = props;
this.behaviors = behaviors;
}

/**
* 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;
}

String elNameString = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, elName.getString());
elNameString += "Element";

Node target;
if (NodeUtil.isNameDeclaration(callNode.getGrandparent())) {
target = IR.name(callNode.getParent().getString());
} else if (callNode.getParent().isAssign()) {
target = callNode.getParent().getFirstChild().cloneTree();
} else {
target = IR.name(elNameString);
}

target.useSourceInfoIfMissingFrom(callNode);
JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(target);

JSDocInfo ctorInfo = null;
Node constructor = NodeUtil.getFirstPropMatchingKey(descriptor, "factoryImpl");
if (constructor == null) {
constructor = IR.function(IR.name(""), IR.paramList(), IR.block());
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<BehaviorDefinition> behaviors = behaviorExtractor.extractBehaviors(behaviorArray);
List<MemberDefinition> allProperties = new LinkedList<>();
for (BehaviorDefinition behavior : behaviors) {
overwriteMembersIfPresent(allProperties, behavior.props);
}
overwriteMembersIfPresent(allProperties, PolymerPassStaticUtils.extractProperties(descriptor));

return new PolymerClassDefinition(
target,
descriptor,
classInfo,
new MemberDefinition(ctorInfo, null, constructor),
nativeBaseElement,
allProperties,
behaviors);
}

/**
* 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<MemberDefinition> list, List<MemberDefinition> newMembers) {
for (MemberDefinition newMember : newMembers) {
for (MemberDefinition member : list) {
if (member.name.getString().equals(newMember.name.getString())) {
list.remove(member);
break;
}
}
list.add(newMember);
}
}
}
Loading

0 comments on commit 0e5edda

Please sign in to comment.