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

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

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs. This binary checks for style issues such as incorrect or missing JSDoc usage, and missing goog.require() statements. It does not do more advanced checks such as typechecking.

There is a newer version: v20200830
Show newest version
/*
 * 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
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.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
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, the main transpilation passes will convert this ES6 code: * *

 *   class Foo { static f() {} }
 *   class Bar extends Foo {}
 * 
* * to this ES3 code: * *
 *   function Foo() {}
 *   Foo.f = function() {};
 *   function Bar() {}
 *   $jscomp.inherits(Bar, Foo);
 * 
* * and then this class will convert that to * *
 *   function Foo() {}
 *   Foo.f = function() {};
 *   function Bar() {}
 *   $jscomp.inherits(Bar, Foo);
 *   Bar.f = Foo.f;
 * 
* * Additionally, there are getter and setter fields which are transpiled from: * *
 *   class Foo { static get prop() { return 1; } }
 *   class Bar extends Foo {}
 * 
* * to: * *
 *   var Foo = function() {};
 *   Foo.prop; // stub declaration so that the type checker knows about prop
 *   Object.defineProperties(Foo, {prop:{get:function() { return 1; }}});
 *
 *   var Bar = function() {};
 *   $jscomp.inherits(Bar, Foo);
 * 
* * The stub declaration of Foo.prop needs to be duplicated for Bar so that the type checker knows * that Bar also has this property. (ES5 clases don't have class-side inheritance). * *
 *   var Bar = function() {};
 *   Bar.prop;
 *   $jscomp.inherits(Bar, Foo);
 * 
* *

In order to gather the type checker declarations, this pass gathers all GETPROPs on * a class. In order to determine which of these are the stub declarations it filters them based * on names discovered in Object.defineProperties. Unfortunately, we cannot simply gather the * defined properties because they don't have the type information (JSDoc). The type information * is stored on the stub declarations so we must gather both to transpile correctly. *

* TODO(tdeegan): In the future the type information for getter/setter properties could be stored * in the defineProperties functions. It would reduce the complexity of this pass significantly. * * @author [email protected] (Matthew Loring) * @author [email protected] (Thomas Deegan) */ public final class Es6ToEs3ClassSideInheritance implements HotSwapCompilerPass { static final DiagnosticType DUPLICATE_CLASS = DiagnosticType.error( "DUPLICATE_CLASS", "Multiple classes cannot share the same name."); private final Set duplicateClassNames = new HashSet<>(); private static class JavascriptClass { // All static members to the class including get set properties. private final Set staticMembers = new LinkedHashSet<>(); // Keep updated the set of static member names to avoid O(n^2) searches. private final Set staticMemberNames = new HashSet<>(); // Collect all the static field accesses to the class. private final Set staticFieldAccess = new LinkedHashSet<>(); // Collect all get set properties as defined by Object.defineProperties(...) private final Set definedProperties = new LinkedHashSet<>(); void addStaticMember(Node node) { staticMembers.add(node); staticMemberNames.add(node.getFirstChild().getLastChild().getString()); } } private final AbstractCompiler compiler; private static final FeatureSet transpiledFeatures = FeatureSet.BARE_MINIMUM.with(Feature.CLASSES); private final LinkedHashMap classByAlias = new LinkedHashMap<>(); public Es6ToEs3ClassSideInheritance(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { FindStaticMembers findStaticMembers = new FindStaticMembers(); TranspilationPasses.processTranspile(compiler, externs, transpiledFeatures, findStaticMembers); TranspilationPasses.processTranspile(compiler, root, transpiledFeatures, findStaticMembers); processInherits(findStaticMembers); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { FindStaticMembers findStaticMembers = new FindStaticMembers(); TranspilationPasses.processTranspile( compiler, scriptRoot, transpiledFeatures, findStaticMembers); processInherits(findStaticMembers); } private void processInherits(FindStaticMembers findStaticMembers) { for (Node inheritsCall : findStaticMembers.inheritsCalls) { Node superclassNameNode = inheritsCall.getLastChild(); String superclassQname = superclassNameNode.getQualifiedName(); Node subclassNameNode = superclassNameNode.getPrevious(); String subclassQname = subclassNameNode.getQualifiedName(); JavascriptClass superClass = classByAlias.get(superclassQname); JavascriptClass subClass = classByAlias.get(subclassQname); if (duplicateClassNames.contains(superclassQname)) { compiler.report(JSError.make(inheritsCall, DUPLICATE_CLASS)); return; } if (superClass == null || subClass == null) { continue; } copyStaticMembers(superClass, subClass, inheritsCall, findStaticMembers); copyDeclarations(superClass, subClass, inheritsCall); } } /** * When static get/set properties are transpiled, in addition to the Object.defineProperties, they * are declared with stub GETPROP declarations so that the type checker understands that these * properties exist on the class. * When subclassing, we also need to declare these properties on the subclass so that the type * checker knows they exist. */ private void copyDeclarations( JavascriptClass superClass, JavascriptClass subClass, Node inheritsCall) { for (Node staticGetProp : superClass.staticFieldAccess) { checkState(staticGetProp.isGetProp()); String memberName = staticGetProp.getLastChild().getString(); // We only copy declarations that have corresponding Object.defineProperties if (!superClass.definedProperties.contains(memberName)) { continue; } // If the subclass already declares the property no need to redeclare it. if (isOverriden(subClass, memberName)) { continue; } Node subclassNameNode = inheritsCall.getSecondChild(); Node getprop = IR.getprop(subclassNameNode.cloneTree(), IR.string(memberName)); JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(staticGetProp.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); compiler.reportChangeToEnclosingScope(parent); // Copy over field access so that subclasses of this subclass can also make the declarations if (!subClass.definedProperties.contains(memberName)) { subClass.staticFieldAccess.add(getprop); subClass.definedProperties.add(memberName); } } } private void copyStaticMembers( JavascriptClass superClass, JavascriptClass subClass, Node inheritsCall, FindStaticMembers findStaticMembers) { for (Node staticMember : superClass.staticMembers) { checkState(staticMember.isAssign(), staticMember); String memberName = staticMember.getFirstChild().getLastChild().getString(); if (superClass.definedProperties.contains(memberName)) { continue; } if (isOverriden(subClass, memberName)) { continue; } if (findStaticMembers.isBefore(inheritsCall, staticMember)) { // Don't copy members that are defined after the $jscomp.inherits call, // since they will not work correctly in IE<11, where static inheritance // is done by copying, rather than prototype manipulation. continue; } JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(staticMember.getJSDocInfo()); Node function = staticMember.getLastChild(); Node sourceInfoNode = function; if (function.isFunction()) { sourceInfoNode = function.getFirstChild(); Node params = NodeUtil.getFunctionParameters(function); checkState(params.isParamList(), params); for (Node param : params.children()) { if (param.getJSDocInfo() != null) { String name = param.getString(); info.recordParameter(name, param.getJSDocInfo().getType()); } } } Node subclassNameNode = inheritsCall.getSecondChild(); Node superclassNameNode = subclassNameNode.getNext(); 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(sourceInfoNode); Node inheritsExpressionResult = inheritsCall.getParent(); inheritsExpressionResult.getParent().addChildAfter(exprResult, inheritsExpressionResult); compiler.reportChangeToEnclosingScope(inheritsExpressionResult); // Add the static member to the subclass so that subclasses also copy this member. subClass.addStaticMember(assign); } } private boolean isOverriden(JavascriptClass subClass, String memberName) { if (subClass.staticMemberNames.contains(memberName)) { // This subclass overrides the static method, so there is no need to copy the // method from the base class. return true; } if (subClass.definedProperties.contains(memberName)) { return true; } return false; } private boolean isReferenceToClass(NodeTraversal t, Node n) { String className = n.getQualifiedName(); if (!classByAlias.containsKey(className)) { return false; } if (!n.isName()) { return true; } Var var = t.getScope().getVar(className); return var == null || !var.isLocal(); } private class FindStaticMembers extends AbstractPostOrderCallback { final List inheritsCalls = new ArrayList<>(); // Store the order we find class definitions and static fields. Copied statics must occur // after both the namespace and the copied property are defined. final Map nodeOrder = new HashMap<>(); @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getToken()) { case CALL: if (n.getFirstChild().matchesQualifiedName(Es6RewriteClass.INHERITS)) { inheritsCalls.add(n); nodeOrder.put(n, nodeOrder.size()); } if (NodeUtil.isObjectDefinePropertiesDefinition(n)) { visitDefinedPropertiesCall(t, n); } break; case CONST: case LET: case VAR: visitVariableDeclaration(n); break; case ASSIGN: visitAssign(t, n); break; case GETPROP: if (parent.isExprResult()) { visitGetProp(t, n); } break; case FUNCTION: visitFunctionClassDef(n); break; default: break; } } private void visitDefinedPropertiesCall(NodeTraversal t, Node definePropertiesCall) { Node object = definePropertiesCall.getSecondChild(); if (isReferenceToClass(t, object)) { String className = object.getQualifiedName(); JavascriptClass c = classByAlias.get(className); for (Node prop : NodeUtil.getObjectDefinedPropertiesKeys(definePropertiesCall)) { c.definedProperties.add(prop.getString()); } } } private void visitFunctionClassDef(Node n) { JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(n); if (classInfo != null && classInfo.isConstructor()) { String name = NodeUtil.getName(n); if (classByAlias.containsKey(name)) { duplicateClassNames.add(name); } else { classByAlias.put(name, new JavascriptClass()); } } } private void setAlias(String original, String alias) { checkArgument(classByAlias.containsKey(original)); classByAlias.put(alias, classByAlias.get(original)); } private void visitGetProp(NodeTraversal t, Node n) { Node classNode = n.getFirstChild(); if (isReferenceToClass(t, classNode)) { classByAlias.get(classNode.getQualifiedName()).staticFieldAccess.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 (existingClassQname != null && classByAlias.containsKey(existingClassQname)) { String alias = n.getFirstChild().getQualifiedName(); if (alias != null) { setAlias(existingClassQname, alias); } } else if (n.getFirstChild().isGetProp()) { Node getProp = n.getFirstChild(); Node classNode = getProp.getFirstChild(); if (isReferenceToClass(t, classNode)) { classByAlias.get(classNode.getQualifiedName()).addStaticMember(n); nodeOrder.put(n, nodeOrder.size()); } } } private void visitVariableDeclaration(Node n) { Node child = n.getFirstChild(); if (!child.hasChildren()) { return; } String maybeOriginalName = child.getFirstChild().getQualifiedName(); if (classByAlias.containsKey(maybeOriginalName)) { String maybeAlias = child.getQualifiedName(); if (maybeAlias != null) { setAlias(maybeOriginalName, maybeAlias); } } } boolean isBefore(Node earlier, Node later) { Integer earlierPosition = nodeOrder.get(earlier); Integer laterPosition = nodeOrder.get(later); return earlierPosition != null && laterPosition != null && earlierPosition < laterPosition; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy