-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactoring and style cleanups for PolymerPass to make things more ma…
…nageable. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=115406177
- Loading branch information
Showing
15 changed files
with
1,981 additions
and
961 deletions.
There are no files selected for viewing
206 changes: 206 additions & 0 deletions
206
src/com/google/javascript/jscomp/PolymerBehaviorExtractor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
161
src/com/google/javascript/jscomp/PolymerClassDefinition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |||
} | |||
} | |||
} |
Oops, something went wrong.