@Override
 public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
   if (n.isObjectLit()) {
     normalizeObjectLiteralAnnotations(n);
   }
   return true;
 }
  /**
   * Expand a jQuery.expandedEach call
   *
   * <p>Expanded jQuery.expandedEach calls will replace the GETELEM nodes of a property assignment
   * with GETPROP nodes to allow for renaming.
   */
  private void maybeExpandJqueryEachCall(NodeTraversal t, Node n) {
    Node objectToLoopOver = n.getChildAtIndex(1);

    if (objectToLoopOver == null) {
      return;
    }

    Node callbackFunction = objectToLoopOver.getNext();
    if (callbackFunction == null || !callbackFunction.isFunction()) {
      return;
    }

    // Run the peephole optimizations on the first argument to handle
    // cases like ("a " + "b").split(" ")
    peepholePasses.process(null, n.getChildAtIndex(1));

    // Create a reference tree
    Node nClone = n.cloneTree();

    objectToLoopOver = nClone.getChildAtIndex(1);

    // Check to see if the first argument is something we recognize and can
    // expand.
    if (!objectToLoopOver.isObjectLit()
        && !(objectToLoopOver.isArrayLit() && isArrayLitValidForExpansion(objectToLoopOver))) {
      t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_LIT_ERROR, (String) null);
      return;
    }

    // Find all references to the callback function arguments
    List<Node> keyNodeReferences = new ArrayList<>();
    List<Node> valueNodeReferences = new ArrayList<>();

    new NodeTraversal(
            compiler,
            new FindCallbackArgumentReferences(
                callbackFunction,
                keyNodeReferences,
                valueNodeReferences,
                objectToLoopOver.isArrayLit()))
        .traverseInnerNode(
            NodeUtil.getFunctionBody(callbackFunction), callbackFunction, t.getScope());

    if (keyNodeReferences.isEmpty()) {
      // We didn't do anything useful ...
      t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null);
      return;
    }

    Node fncBlock =
        tryExpandJqueryEachCall(
            t, nClone, callbackFunction, keyNodeReferences, valueNodeReferences);

    if (fncBlock != null && fncBlock.hasChildren()) {
      replaceOriginalJqueryEachCall(n, fncBlock);
    } else {
      // We didn't do anything useful ...
      t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null);
    }
  }
  /** Try to fold array-length. e.g [1, 2, 3].length ==> 3, [x, y].length ==> 2 */
  private Node tryFoldGetProp(Node n, Node left, Node right) {
    Preconditions.checkArgument(n.isGetProp());

    if (left.isObjectLit()) {
      return tryFoldObjectPropAccess(n, left, right);
    }

    if (right.isString() && right.getString().equals("length")) {
      int knownLength = -1;
      switch (left.getType()) {
        case ARRAYLIT:
          if (mayHaveSideEffects(left)) {
            // Nope, can't fold this, without handling the side-effects.
            return n;
          }
          knownLength = left.getChildCount();
          break;
        case STRING:
          knownLength = left.getString().length();
          break;
        default:
          // Not a foldable case, forget it.
          return n;
      }

      Preconditions.checkState(knownLength != -1);
      Node lengthNode = IR.number(knownLength);
      n.getParent().replaceChild(n, lengthNode);
      reportCodeChange();

      return lengthNode;
    }

    return n;
  }
 private void normalizeObjectLiteralAnnotations(Node objlit) {
   Preconditions.checkState(objlit.isObjectLit());
   for (Node key = objlit.getFirstChild(); key != null; key = key.getNext()) {
     Node value = key.getFirstChild();
     normalizeObjectLiteralKeyAnnotations(objlit, key, value);
   }
 }
  /** Add an @this annotation to all functions in the objLit. */
  private void addTypesToFunctions(Node objLit, String thisType) {
    Preconditions.checkState(objLit.isObjectLit());
    for (Node keyNode : objLit.children()) {
      Node value = keyNode.getLastChild();
      if (value != null && value.isFunction()) {
        JSDocInfoBuilder fnDoc = JSDocInfoBuilder.maybeCopyFrom(keyNode.getJSDocInfo());
        fnDoc.recordThisType(
            new JSTypeExpression(new Node(Token.BANG, IR.string(thisType)), VIRTUAL_FILE));
        keyNode.setJSDocInfo(fnDoc.build());
      }
    }

    // Add @this and @return to default property values.
    for (MemberDefinition property : extractProperties(objLit)) {
      if (!property.value.isObjectLit()) {
        continue;
      }
      if (hasShorthandAssignment(property.value)) {
        compiler.report(JSError.make(property.value, POLYMER_SHORTHAND_NOT_SUPPORTED));
        return;
      }

      Node defaultValue = NodeUtil.getFirstPropMatchingKey(property.value, "value");
      if (defaultValue == null || !defaultValue.isFunction()) {
        continue;
      }
      Node defaultValueKey = defaultValue.getParent();
      JSDocInfoBuilder fnDoc = JSDocInfoBuilder.maybeCopyFrom(defaultValueKey.getJSDocInfo());
      fnDoc.recordThisType(
          new JSTypeExpression(new Node(Token.BANG, IR.string(thisType)), VIRTUAL_FILE));
      fnDoc.recordReturnType(getTypeFromProperty(property));
      defaultValueKey.setJSDocInfo(fnDoc.build());
    }
  }
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   switch (n.getType()) {
     case Token.OBJECTLIT:
       for (Node child : n.children()) {
         if (child.isComputedProp()) {
           visitObjectWithComputedProperty(n, parent);
           break;
         }
       }
       break;
     case Token.MEMBER_DEF:
       if (parent.isObjectLit()) {
         visitMemberDefInObjectLit(n, parent);
       }
       break;
     case Token.FOR_OF:
       visitForOf(n, parent);
       break;
     case Token.SUPER:
       visitSuper(n, parent);
       break;
     case Token.STRING_KEY:
       visitStringKey(n);
       break;
     case Token.CLASS:
       for (Node member = n.getLastChild().getFirstChild();
           member != null;
           member = member.getNext()) {
         if (member.isGetterDef()
             || member.isSetterDef()
             || member.getBooleanProp(Node.COMPUTED_PROP_GETTER)
             || member.getBooleanProp(Node.COMPUTED_PROP_SETTER)) {
           cannotConvert(member, "getters or setters in class definitions");
           return;
         }
       }
       visitClass(n, parent);
       break;
     case Token.ARRAYLIT:
     case Token.NEW:
     case Token.CALL:
       for (Node child : n.children()) {
         if (child.isSpread()) {
           visitArrayLitOrCallWithSpread(n, parent);
           break;
         }
       }
       break;
     case Token.TEMPLATELIT:
       Es6TemplateLiterals.visitTemplateLiteral(t, n);
       break;
     case Token.ARRAY_PATTERN:
       visitArrayPattern(t, n, parent);
       break;
     case Token.OBJECT_PATTERN:
       visitObjectPattern(t, n, parent);
       break;
   }
 }
 private boolean hasShorthandAssignment(Node objLit) {
   Preconditions.checkState(objLit.isObjectLit());
   for (Node property : objLit.children()) {
     if (property.isStringKey() && !property.hasChildren()) {
       return true;
     }
   }
   return false;
 }
  private void visitObjectWithComputedProperty(Node obj, Node parent) {
    Preconditions.checkArgument(obj.isObjectLit());
    List<Node> props = new ArrayList<>();
    Node currElement = obj.getFirstChild();

    while (currElement != null) {
      if (currElement.getBooleanProp(Node.COMPUTED_PROP_GETTER)
          || currElement.getBooleanProp(Node.COMPUTED_PROP_SETTER)) {
        cannotConvertYet(currElement, "computed getter/setter");
        return;
      } else if (currElement.isGetterDef() || currElement.isSetterDef()) {
        currElement = currElement.getNext();
      } else {
        Node nextNode = currElement.getNext();
        obj.removeChild(currElement);
        props.add(currElement);
        currElement = nextNode;
      }
    }

    String objName = FRESH_COMP_PROP_VAR + compiler.getUniqueNameIdSupplier().get();

    props = Lists.reverse(props);
    Node result = IR.name(objName);
    for (Node propdef : props) {
      if (propdef.isComputedProp()) {
        Node propertyExpression = propdef.removeFirstChild();
        Node value = propdef.removeFirstChild();
        result =
            IR.comma(IR.assign(IR.getelem(IR.name(objName), propertyExpression), value), result);
      } else {
        if (!propdef.hasChildren()) {
          Node name = IR.name(propdef.getString()).useSourceInfoIfMissingFrom(propdef);
          propdef.addChildToBack(name);
        }
        Node val = propdef.removeFirstChild();
        propdef.setType(Token.STRING);
        int type = propdef.isQuotedString() ? Token.GETELEM : Token.GETPROP;
        Node access = new Node(type, IR.name(objName), propdef);
        result = IR.comma(IR.assign(access, val), result);
      }
    }

    Node statement = obj;
    while (!NodeUtil.isStatement(statement)) {
      statement = statement.getParent();
    }

    result.useSourceInfoIfMissingFromForTree(obj);
    parent.replaceChild(obj, result);

    Node var = IR.var(IR.name(objName), obj);
    var.useSourceInfoIfMissingFromForTree(statement);
    statement.getParent().addChildBefore(var, statement);
    compiler.reportCodeChange();
  }
  /**
   * Check that Node n is a call to one of the jQuery.extend methods that we can expand. Valid calls
   * are single argument calls where the first argument is an object literal or two argument calls
   * where the first argument is a name and the second argument is an object literal.
   */
  public static boolean isJqueryExtendCall(Node n, String qname, AbstractCompiler compiler) {
    if (JQUERY_EXTEND_NAMES.contains(qname)) {
      Node firstArgument = n.getNext();
      if (firstArgument == null) {
        return false;
      }

      Node secondArgument = firstArgument.getNext();
      if ((firstArgument.isObjectLit() && secondArgument == null)
          || (firstArgument.isName()
              || NodeUtil.isGet(firstArgument)
                  && !NodeUtil.mayHaveSideEffects(firstArgument, compiler)
                  && secondArgument != null
                  && secondArgument.isObjectLit()
                  && secondArgument.getNext() == null)) {
        return true;
      }
    }
    return false;
  }
 private boolean isContainedInGoogDefineClass(Node n) {
   while (n != null) {
     n = n.getParent();
     if (n.isCall()) {
       if (isGoogDefineClass(n)) {
         return true;
       }
     } else if (!n.isObjectLit() && !n.isStringKey()) {
       break;
     }
   }
   return false;
 }
  /** Remove useless calls: Object.defineProperties(o, {}) -> o */
  private Node tryFoldCall(Node n) {
    Preconditions.checkArgument(n.isCall());

    if (NodeUtil.isObjectDefinePropertiesDefinition(n)) {
      Node srcObj = n.getLastChild();
      if (srcObj.isObjectLit() && !srcObj.hasChildren()) {
        Node parent = n.getParent();
        Node destObj = n.getSecondChild().detachFromParent();
        parent.replaceChild(n, destObj);
        reportCodeChange();
      }
    }
    return n;
  }
    /** Strip property type annotations and add suppress checkTypes and globalThis on functions. */
    private void suppressBehavior(Node behaviorValue) {
      if (behaviorValue == null) {
        compiler.report(JSError.make(behaviorValue, POLYMER_UNQUALIFIED_BEHAVIOR));
        return;
      }

      if (behaviorValue.isArrayLit()) {
        for (Node child : behaviorValue.children()) {
          suppressBehavior(child);
        }
      } else if (behaviorValue.isObjectLit()) {
        stripPropertyTypes(behaviorValue);
        addBehaviorSuppressions(behaviorValue);
      }
    }
 /**
  * Makes sure that the keys for listeners and hostAttributes blocks are quoted to avoid renaming.
  */
 private void quoteListenerAndHostAttributeKeys(Node objLit) {
   Preconditions.checkState(objLit.isObjectLit());
   for (Node keyNode : objLit.children()) {
     if (keyNode.isComputedProp()) {
       continue;
     }
     if (!keyNode.getString().equals("listeners")
         && !keyNode.getString().equals("hostAttributes")) {
       continue;
     }
     for (Node keyToQuote : keyNode.getFirstChild().children()) {
       keyToQuote.setQuotedString();
     }
   }
 }
  /** Try to fold array-element. e.g [1, 2, 3][10]; */
  private Node tryFoldGetElem(Node n, Node left, Node right) {
    Preconditions.checkArgument(n.isGetElem());

    if (left.isObjectLit()) {
      return tryFoldObjectPropAccess(n, left, right);
    }

    if (left.isArrayLit()) {
      return tryFoldArrayAccess(n, left, right);
    }

    if (left.isString()) {
      return tryFoldStringArrayAccess(n, left, right);
    }
    return n;
  }
 ClassDefinition(
     Node target,
     Node descriptor,
     JSDocInfo classInfo,
     MemberDefinition constructor,
     String nativeBaseElement,
     List<MemberDefinition> props,
     List<BehaviorDefinition> behaviors) {
   this.target = target;
   Preconditions.checkState(descriptor.isObjectLit());
   this.descriptor = descriptor;
   this.constructor = constructor;
   this.nativeBaseElement = nativeBaseElement;
   this.props = props;
   this.behaviors = behaviors;
 }
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   if (n.isFunction()) {
     checkFunctionUse(t, n);
   } else if (n.isAssign()) {
     checkAssignment(t, n);
   } else if (n.isDelProp()) {
     checkDelete(t, n);
   } else if (n.isObjectLit()) {
     checkObjectLiteralOrClass(t, n);
   } else if (n.isClass()) {
     checkObjectLiteralOrClass(t, n.getLastChild());
   } else if (n.isWith()) {
     checkWith(t, n);
   }
 }
  /**
   * @return A list of functions from a behavior which should be copied to the element prototype.
   */
  private List<MemberDefinition> getBehaviorFunctionsToCopy(Node behaviorObjLit) {
    Preconditions.checkState(behaviorObjLit.isObjectLit());
    ImmutableList.Builder<MemberDefinition> functionsToCopy = ImmutableList.builder();

    for (Node keyNode : behaviorObjLit.children()) {
      if ((keyNode.isStringKey() && keyNode.getFirstChild().isFunction()
              || keyNode.isMemberFunctionDef())
          && !behaviorNamesNotToCopy.contains(keyNode.getString())) {
        functionsToCopy.add(
            new MemberDefinition(
                NodeUtil.getBestJSDocInfo(keyNode), keyNode, keyNode.getFirstChild()));
      }
    }

    return functionsToCopy.build();
  }
  /**
   * Updates the first initialization (a.k.a "declaration") of a global name that occurs at a VAR
   * node. See comment for {@link #updateObjLitOrFunctionDeclaration}.
   *
   * @param n An object representing a global name (e.g. "a")
   */
  private void updateObjLitOrFunctionDeclarationAtVarNode(Name n, boolean canCollapseChildNames) {
    if (!canCollapseChildNames) {
      return;
    }

    Ref ref = n.getDeclaration();
    String name = ref.node.getString();
    Node rvalue = ref.node.getFirstChild();
    Node varNode = ref.node.getParent();
    Node grandparent = varNode.getParent();

    boolean isObjLit = rvalue.isObjectLit();
    int numChanges = 0;

    if (isObjLit) {
      numChanges +=
          declareVarsForObjLitValues(
              n, name, rvalue, varNode, grandparent.getChildBefore(varNode), grandparent);
    }

    numChanges += addStubsForUndeclaredProperties(n, name, grandparent, varNode);

    if (isObjLit && n.canEliminate()) {
      varNode.removeChild(ref.node);
      if (!varNode.hasChildren()) {
        grandparent.removeChild(varNode);
      }
      numChanges++;

      // Clear out the object reference, since we've eliminated it from the
      // parse tree.
      ref.node = null;
    }

    if (numChanges > 0) {
      compiler.reportCodeChange();
    }
  }
 /** Switches all "this.$.foo" to "this.$['foo']". */
 private void switchDollarSignPropsToBrackets(Node objLit) {
   Preconditions.checkState(objLit.isObjectLit());
   for (Node keyNode : objLit.children()) {
     Node value = keyNode.getFirstChild();
     if (value != null && value.isFunction()) {
       NodeUtil.visitPostOrder(
           value.getLastChild(),
           new NodeUtil.Visitor() {
             @Override
             public void visit(Node n) {
               if (n.isString()
                   && n.getString().equals("$")
                   && n.getParent().isGetProp()
                   && n.getParent().getParent().isGetProp()) {
                 Node dollarChildProp = n.getParent().getParent();
                 dollarChildProp.setType(Token.GETELEM);
                 compiler.reportCodeChange();
               }
             }
           },
           Predicates.<Node>alwaysTrue());
     }
   }
 }
  /**
   * Validates the class definition and if valid, destructively extracts the class definition from
   * the AST.
   */
  private ClassDefinition extractClassDefinition(Node targetName, Node callNode) {

    JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(targetName);

    // name = goog.defineClass(superClass, {...}, [modifier, ...])
    Node superClass = NodeUtil.getArgumentForCallOrNew(callNode, 0);
    if (superClass == null || (!superClass.isNull() && !superClass.isQualifiedName())) {
      compiler.report(JSError.make(callNode, GOOG_CLASS_SUPER_CLASS_NOT_VALID));
      return null;
    }

    if (NodeUtil.isNullOrUndefined(superClass) || superClass.matchesQualifiedName("Object")) {
      superClass = null;
    }

    Node description = NodeUtil.getArgumentForCallOrNew(callNode, 1);
    if (description == null || !description.isObjectLit() || !validateObjLit(description)) {
      // report bad class definition
      compiler.report(JSError.make(callNode, GOOG_CLASS_DESCRIPTOR_NOT_VALID));
      return null;
    }

    int paramCount = callNode.getChildCount() - 1;
    if (paramCount > 2) {
      compiler.report(JSError.make(callNode, GOOG_CLASS_UNEXPECTED_PARAMS));
      return null;
    }

    Node constructor = extractProperty(description, "constructor");
    if (classInfo != null && classInfo.isInterface()) {
      if (constructor != null) {
        compiler.report(JSError.make(description, GOOG_CLASS_CONSTRUCTOR_ON_INTERFACE));
        return null;
      }
    } else if (constructor == null) {
      // report missing constructor
      compiler.report(JSError.make(description, GOOG_CLASS_CONSTRUCTOR_MISSING));
      return null;
    }
    if (constructor == null) {
      constructor =
          IR.function(
              IR.name("").srcref(callNode),
              IR.paramList().srcref(callNode),
              IR.block().srcref(callNode));
      constructor.srcref(callNode);
    }

    JSDocInfo info = NodeUtil.getBestJSDocInfo(constructor);

    Node classModifier = null;
    Node statics = null;
    Node staticsProp = extractProperty(description, "statics");
    if (staticsProp != null) {
      if (staticsProp.isObjectLit() && validateObjLit(staticsProp)) {
        statics = staticsProp;
      } else if (staticsProp.isFunction()) {
        classModifier = staticsProp;
      } else {
        compiler.report(JSError.make(staticsProp, GOOG_CLASS_STATICS_NOT_VALID));
        return null;
      }
    }

    if (statics == null) {
      statics = IR.objectlit();
    }

    // Ok, now rip apart the definition into its component pieces.
    // Remove the "special" property key nodes.
    maybeDetach(constructor.getParent());
    maybeDetach(statics.getParent());
    if (classModifier != null) {
      maybeDetach(classModifier.getParent());
    }
    ClassDefinition def =
        new ClassDefinition(
            targetName,
            classInfo,
            maybeDetach(superClass),
            new MemberDefinition(info, null, maybeDetach(constructor)),
            objectLitToList(maybeDetach(statics)),
            objectLitToList(description),
            maybeDetach(classModifier));
    return def;
  }
Example #21
0
 /**
  * In the AST that Rhino gives us, it needs to make a distinction between JsDoc on the object
  * literal node and JsDoc on the object literal value. For example,
  *
  * <pre>
  * var x = {
  *   / JSDOC /
  *   a: 'b',
  *   c: / JSDOC / 'd'
  * };
  * </pre>
  *
  * But in few narrow cases (in particular, function literals), it's a lot easier for us if the
  * doc is attached to the value.
  */
 private static void normalizeObjectLiteralKeyAnnotations(Node objlit, Node key, Node value) {
   Preconditions.checkState(objlit.isObjectLit());
   if (key.getJSDocInfo() != null && value.isFunction()) {
     value.setJSDocInfo(key.getJSDocInfo());
   }
 }
  private Node tryFoldObjectPropAccess(Node n, Node left, Node right) {
    Preconditions.checkArgument(NodeUtil.isGet(n));

    if (!left.isObjectLit() || !right.isString()) {
      return n;
    }

    if (NodeUtil.isAssignmentTarget(n)) {
      // If GETPROP/GETELEM is used as assignment target the object literal is
      // acting as a temporary we can't fold it here:
      //    "{a:x}.a += 1" is not "x += 1"
      return n;
    }

    // find the last definition in the object literal
    Node key = null;
    Node value = null;
    for (Node c = left.getFirstChild(); c != null; c = c.getNext()) {
      if (c.getString().equals(right.getString())) {
        switch (c.getType()) {
          case SETTER_DEF:
            continue;
          case GETTER_DEF:
          case STRING_KEY:
            if (value != null && mayHaveSideEffects(value)) {
              // The previously found value had side-effects
              return n;
            }
            key = c;
            value = key.getFirstChild();
            break;
          default:
            throw new IllegalStateException();
        }
      } else if (mayHaveSideEffects(c.getFirstChild())) {
        // We don't handle the side-effects here as they might need a temporary
        // or need to be reordered.
        return n;
      }
    }

    // Didn't find a definition of the name in the object literal, it might
    // be coming from the Object prototype
    if (value == null) {
      return n;
    }

    if (value.isFunction() && NodeUtil.referencesThis(value)) {
      // 'this' may refer to the object we are trying to remove
      return n;
    }

    Node replacement = value.detachFromParent();
    if (key.isGetterDef()) {
      replacement = IR.call(replacement);
      replacement.putBooleanProp(Node.FREE_CALL, true);
    }

    n.getParent().replaceChild(n, replacement);
    reportCodeChange();
    return n;
  }
  /**
   * Validates the class definition and if valid, destructively extracts the class definition from
   * the AST.
   */
  private ClassDefinition extractClassDefinition(Node callNode) {
    Node descriptor = NodeUtil.getArgumentForCallOrNew(callNode, 0);
    if (descriptor == null || !descriptor.isObjectLit()) {
      // report bad class definition
      compiler.report(JSError.make(callNode, POLYMER_DESCRIPTOR_NOT_VALID));
      return null;
    }

    int paramCount = callNode.getChildCount() - 1;
    if (paramCount != 1) {
      compiler.report(JSError.make(callNode, POLYMER_UNEXPECTED_PARAMS));
      return null;
    }

    Node elName = NodeUtil.getFirstPropMatchingKey(descriptor, "is");
    if (elName == null) {
      compiler.report(JSError.make(callNode, POLYMER_MISSING_IS));
      return null;
    }

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

    Node target;
    if (NodeUtil.isNameDeclaration(callNode.getParent().getParent())) {
      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");
    List<BehaviorDefinition> behaviors = extractBehaviors(behaviorArray);
    List<MemberDefinition> allProperties = new LinkedList<>();
    for (BehaviorDefinition behavior : behaviors) {
      overwriteMembersIfPresent(allProperties, behavior.props);
    }
    overwriteMembersIfPresent(allProperties, extractProperties(descriptor));

    ClassDefinition def =
        new ClassDefinition(
            target,
            descriptor,
            classInfo,
            new MemberDefinition(ctorInfo, null, constructor),
            nativeBaseElement,
            allProperties,
            behaviors);
    return def;
  }
  /**
   * Extracts all Behaviors from an array recursively. The array must be an array literal whose
   * value is known at compile-time. 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.
   */
  private List<BehaviorDefinition> extractBehaviors(Node behaviorArray) {
    if (behaviorArray == null) {
      return ImmutableList.of();
    }

    if (!behaviorArray.isArrayLit()) {
      compiler.report(JSError.make(behaviorArray, POLYMER_INVALID_BEHAVIOR_ARRAY));
      return ImmutableList.of();
    }

    ImmutableList.Builder<BehaviorDefinition> behaviors = ImmutableList.builder();
    for (Node behaviorName : behaviorArray.children()) {
      if (behaviorName.isObjectLit()) {
        this.switchDollarSignPropsToBrackets(behaviorName);
        this.quoteListenerAndHostAttributeKeys(behaviorName);
        behaviors.add(
            new BehaviorDefinition(
                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, 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, POLYMER_UNQUALIFIED_BEHAVIOR));
        continue;
      }

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

      Node behaviorValue = NodeUtil.getRValueOfLValue(behaviorDeclarationNode);

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

    return behaviors.build();
  }
  /**
   * Updates the first initialization (a.k.a "declaration") of a global name that occurs at an
   * ASSIGN node. See comment for {@link #updateObjLitOrFunctionDeclaration}.
   *
   * @param n An object representing a global name (e.g. "a", "a.b.c")
   * @param alias The flattened name for {@code n} (e.g. "a", "a$b$c")
   */
  private void updateObjLitOrFunctionDeclarationAtAssignNode(
      Name n, String alias, boolean canCollapseChildNames) {
    // NOTE: It's important that we don't add additional nodes
    // (e.g. a var node before the exprstmt) because the exprstmt might be
    // the child of an if statement that's not inside a block).

    Ref ref = n.getDeclaration();
    Node rvalue = ref.node.getNext();
    Node varNode = new Node(Token.VAR);
    Node varParent = ref.node.getAncestor(3);
    Node grandparent = ref.node.getAncestor(2);
    boolean isObjLit = rvalue.isObjectLit();
    boolean insertedVarNode = false;

    if (isObjLit && n.canEliminate()) {
      // Eliminate the object literal altogether.
      varParent.replaceChild(grandparent, varNode);
      ref.node = null;
      insertedVarNode = true;

    } else if (!n.isSimpleName()) {
      // Create a VAR node to declare the name.
      if (rvalue.isFunction()) {
        checkForHosedThisReferences(rvalue, n.docInfo, n);
      }

      ref.node.getParent().removeChild(rvalue);

      Node nameNode = NodeUtil.newName(compiler, alias, ref.node.getAncestor(2), n.getFullName());

      JSDocInfo info = NodeUtil.getBestJSDocInfo(ref.node.getParent());
      if (ref.node.getLastChild().getBooleanProp(Node.IS_CONSTANT_NAME)
          || (info != null && info.isConstant())) {
        nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
      }

      if (info != null) {
        varNode.setJSDocInfo(info);
      }
      varNode.addChildToBack(nameNode);
      nameNode.addChildToFront(rvalue);
      varParent.replaceChild(grandparent, varNode);

      // Update the node ancestry stored in the reference.
      ref.node = nameNode;
      insertedVarNode = true;
    }

    if (canCollapseChildNames) {
      if (isObjLit) {
        declareVarsForObjLitValues(
            n, alias, rvalue, varNode, varParent.getChildBefore(varNode), varParent);
      }

      addStubsForUndeclaredProperties(n, alias, varParent, varNode);
    }

    if (insertedVarNode) {
      if (!varNode.hasChildren()) {
        varParent.removeChild(varNode);
      }
      compiler.reportCodeChange();
    }
  }