com.google.javascript.jscomp.PolymerClassDefinition.java Source code

Java tutorial

Introduction

Here is the source code for com.google.javascript.jscomp.PolymerClassDefinition.java

Source

/*
 * 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.MoreObjects.toStringHelper;
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<MemberDefinition> props;

    /** Methods on the element. */
    @Nullable
    final List<MemberDefinition> methods;

    /** Flattened list of behavior definitions used by this element. */
    @Nullable
    final ImmutableList<BehaviorDefinition> 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<MemberDefinition> props, List<MemberDefinition> methods,
            ImmutableList<BehaviorDefinition> 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.methods = methods;
        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<BehaviorDefinition> behaviors = behaviorExtractor.extractBehaviors(behaviorArray);
        List<MemberDefinition> allProperties = new ArrayList<>();
        for (BehaviorDefinition behavior : behaviors) {
            overwriteMembersIfPresent(allProperties, behavior.props);
        }
        overwriteMembersIfPresent(allProperties,
                PolymerPassStaticUtils.extractProperties(descriptor, DefinitionType.ObjectLiteral, compiler,
                        /** constructor= */
                        null));

        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);
            }
        }

        List<MemberDefinition> methods = new ArrayList<>();
        for (Node keyNode : descriptor.children()) {
            boolean isFunctionDefinition = keyNode.isMemberFunctionDef()
                    || (keyNode.isStringKey() && keyNode.getFirstChild().isFunction());
            if (isFunctionDefinition) {
                methods.add(
                        new MemberDefinition(NodeUtil.getBestJSDocInfo(keyNode), keyNode, keyNode.getFirstChild()));
            }
        }

        return new PolymerClassDefinition(DefinitionType.ObjectLiteral, callNode, target, descriptor, classInfo,
                new MemberDefinition(ctorInfo, null, constructor), nativeBaseElement, allProperties, methods,
                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<MemberDefinition> allProperties = PolymerPassStaticUtils.extractProperties(propertiesDescriptor,
                DefinitionType.ES6Class, compiler, constructor);

        List<MemberDefinition> methods = new ArrayList<>();
        for (Node keyNode : NodeUtil.getClassMembers(classNode).children()) {
            if (!keyNode.isMemberFunctionDef()) {
                continue;
            }
            methods.add(new MemberDefinition(NodeUtil.getBestJSDocInfo(keyNode), keyNode, keyNode.getFirstChild()));
        }

        return new PolymerClassDefinition(DefinitionType.ES6Class, classNode, target, propertiesDescriptor,
                classInfo, new MemberDefinition(ctorInfo, null, constructor), null, allProperties, methods, 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<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);
        }
    }

    @Override
    public String toString() {
        return toStringHelper(this).add("defType", defType).add("definition", definition).add("target", target)
                .add("nativeBaseElement", nativeBaseElement).omitNullValues().toString();
    }
}