All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.PolymerClassDefinition Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * 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);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy