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

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

/*
 * Copyright 2014 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.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * Rewrites static inheritance to explicitly copy inherited properties from superclass to
 * subclass so that the typechecker knows the subclass has those properties.
 *
 * 

For example, {@link Es6ToEs3Converter} will convert * *

 *   class Foo { static f() {} }
 *   class Bar extends Foo {}
 * 
* to * *
 *   function Foo() {}
 *   Foo.f = function() {};
 *   function Bar() {}
 *   $jscomp.inherits(Foo, Bar);
 * 
* * and then this class will convert that to * *
 *   function Foo() {}
 *   Foo.f = function() {};
 *   function Bar() {}
 *   $jscomp.inherits(Foo, Bar);
 *   Bar.f = Foo.f;
 * 
* * @author [email protected] (Matthew Loring) */ public final class Es6ToEs3ClassSideInheritance implements HotSwapCompilerPass { private final AbstractCompiler compiler; // Map from class names to the static members in each class. This is not a SetMultiMap because // when we find an alias A for a class C, we copy the *set* of C's static methods // so that adding a method to C automatically causes it to be added to A, and vice versa. private final LinkedHashMap> staticMethods = new LinkedHashMap<>(); // Map from class names to static properties (getters/setters) in each class. This could be a // SetMultimap but it is not, to be consistent with {@code staticMethods}. private final LinkedHashMap> staticProperties = new LinkedHashMap<>(); static final DiagnosticType DUPLICATE_CLASS = DiagnosticType.error( "DUPLICATE_CLASS", "Multiple classes cannot share the same name."); private final Set multiplyDefinedClasses = new HashSet<>(); public Es6ToEs3ClassSideInheritance(AbstractCompiler compiler) { this.compiler = compiler; } private LinkedHashSet getSet(LinkedHashMap> map, String key) { LinkedHashSet s = map.get(key); if (s == null) { s = new LinkedHashSet<>(); map.put(key, s); } return s; } @Override public void process(Node externs, Node root) { FindStaticMembers findStaticMembers = new FindStaticMembers(); NodeTraversal.traverseEs6(compiler, externs, findStaticMembers); NodeTraversal.traverseEs6(compiler, root, findStaticMembers); processInherits(findStaticMembers.inheritsCalls); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { FindStaticMembers findStaticMembers = new FindStaticMembers(); NodeTraversal.traverseEs6(compiler, scriptRoot, findStaticMembers); processInherits(findStaticMembers.inheritsCalls); } private void processInherits(List inheritsCalls) { for (Node n : inheritsCalls) { Node parent = n.getParent(); Node superclassNameNode = n.getLastChild(); String superclassQname = superclassNameNode.getQualifiedName(); Node subclassNameNode = n.getChildBefore(superclassNameNode); String subclassQname = subclassNameNode.getQualifiedName(); if (multiplyDefinedClasses.contains(superclassQname)) { compiler.report(JSError.make(n, DUPLICATE_CLASS)); return; } for (Node staticMethod : getSet(staticMethods, superclassQname)) { copyStaticMethod(staticMethod, superclassNameNode, subclassNameNode, subclassQname, parent); } for (Node staticProperty : getSet(staticProperties, superclassQname)) { copyStaticProperty(staticProperty, subclassNameNode, subclassQname, n); } } } private void copyStaticMethod(Node staticMember, Node superclassNameNode, Node subclassNameNode, String subclassQname, Node insertionPoint) { Preconditions.checkState(staticMember.isAssign(), staticMember); String memberName = staticMember.getFirstChild().getLastChild().getString(); LinkedHashSet subclassMethods = getSet(staticMethods, subclassQname); for (Node subclassMember : subclassMethods) { Preconditions.checkState(subclassMember.isAssign(), subclassMember); if (subclassMember.getFirstChild().getLastChild().getString().equals(memberName)) { // This subclass overrides the static method, so there is no need to copy the // method from the base class. return; } } JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(staticMember.getJSDocInfo()); Node function = staticMember.getLastChild(); if (function.isFunction()) { Node params = NodeUtil.getFunctionParameters(function); Preconditions.checkState(params.isParamList(), params); for (Node param : params.children()) { if (param.getJSDocInfo() != null) { String name = param.getString(); info.recordParameter(name, param.getJSDocInfo().getType()); } } } Node assign = IR.assign( IR.getprop(subclassNameNode.cloneTree(), IR.string(memberName)), IR.getprop(superclassNameNode.cloneTree(), IR.string(memberName))); info.addSuppression("visibility"); assign.setJSDocInfo(info.build()); Node exprResult = IR.exprResult(assign); exprResult.useSourceInfoIfMissingFromForTree(superclassNameNode); insertionPoint.getParent().addChildAfter(exprResult, insertionPoint); subclassMethods.add(assign); compiler.reportCodeChange(); } private void copyStaticProperty(Node staticProperty, Node subclassNameNode, String subclassQname, Node inheritsCall) { Preconditions.checkState(staticProperty.isGetProp(), staticProperty); String memberName = staticProperty.getLastChild().getString(); LinkedHashSet subclassProps = getSet(staticProperties, subclassQname); for (Node subclassMember : getSet(staticProperties, subclassQname)) { Preconditions.checkState(subclassMember.isGetProp()); if (subclassMember.getLastChild().getString().equals(memberName)) { return; } } // Add a declaration. Assuming getters are side-effect free, // this is a no-op statement, but it lets the typechecker know about the property. Node getprop = IR.getprop(subclassNameNode.cloneTree(), IR.string(memberName)); JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(staticProperty.getJSDocInfo()); JSTypeExpression unknown = new JSTypeExpression(new Node(Token.QMARK), ""); info.recordType(unknown); // In case there wasn't a type specified on the base class. info.addSuppression("visibility"); getprop.setJSDocInfo(info.build()); Node declaration = IR.exprResult(getprop); declaration.useSourceInfoIfMissingFromForTree(inheritsCall); Node parent = inheritsCall.getParent(); parent.getParent().addChildBefore(declaration, parent); subclassProps.add(staticProperty); compiler.reportCodeChange(); } private class FindStaticMembers extends NodeTraversal.AbstractPostOrderCallback { private final Set classNames = new HashSet<>(); private final List inheritsCalls = new LinkedList<>(); @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: if (n.getFirstChild().matchesQualifiedName(Es6ToEs3Converter.INHERITS)) { inheritsCalls.add(n); } break; case Token.VAR: visitVar(n); break; case Token.ASSIGN: visitAssign(t, n); break; case Token.GETPROP: if (parent.isExprResult()) { visitGetProp(t, n); } break; case Token.FUNCTION: visitFunctionClassDef(n); break; } } private void visitFunctionClassDef(Node n) { JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(n); if (classInfo != null && classInfo.isConstructor()) { String name = NodeUtil.getName(n); if (classNames.contains(name)) { multiplyDefinedClasses.add(name); } else if (name != null) { classNames.add(name); } } } private void visitGetProp(NodeTraversal t, Node n) { Node classNode = n.getFirstChild(); if (isReferenceToClass(t, classNode)) { getSet(staticProperties, classNode.getQualifiedName()).add(n); } } private void visitAssign(NodeTraversal t, Node n) { // Alias for classes. We assume that the alias appears after the class // declaration. String existingClassQname = n.getLastChild().getQualifiedName(); if (classNames.contains(existingClassQname)) { String maybeAlias = n.getFirstChild().getQualifiedName(); if (maybeAlias != null) { classNames.add(maybeAlias); staticMethods.put(maybeAlias, getSet(staticMethods, existingClassQname)); } } else if (n.getFirstChild().isGetProp()) { Node getProp = n.getFirstChild(); Node classNode = getProp.getFirstChild(); if (isReferenceToClass(t, classNode)) { getSet(staticMethods, classNode.getQualifiedName()).add(n); } } } private boolean isReferenceToClass(NodeTraversal t, Node n) { String className = n.getQualifiedName(); if (!classNames.contains(className)) { return false; } if (!n.isName()) { return true; } Var var = t.getScope().getVar(className); return var == null || !var.isLocal(); } private void visitVar(Node n) { Node child = n.getFirstChild(); if (!child.hasChildren()) { return; } String maybeOriginalName = child.getFirstChild().getQualifiedName(); if (classNames.contains(maybeOriginalName)) { String maybeAlias = child.getQualifiedName(); if (maybeAlias != null) { classNames.add(maybeAlias); staticMethods.put(maybeAlias, getSet(staticMethods, maybeOriginalName)); } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy