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

com.google.javascript.jscomp.Es6ConvertSuper 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.Es6ToEs3Util.CANNOT_CONVERT_YET;

import com.google.common.base.Joiner;
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.Node;
import com.google.javascript.rhino.jstype.JSType;

/**
 * Converts {@code super.method()} calls and adds constructors to any classes that lack them.
 *
 * 

This has to run before the main {@link Es6RewriteClass} pass. The super() constructor calls * are not converted here, but rather in {@link Es6ConvertSuperConstructorCalls}, which runs later. */ public final class Es6ConvertSuper extends NodeTraversal.AbstractPostOrderCallback implements HotSwapCompilerPass { private final AbstractCompiler compiler; private final AstFactory astFactory; private static final FeatureSet transpiledFeatures = FeatureSet.BARE_MINIMUM.with(Feature.SUPER); public Es6ConvertSuper(AbstractCompiler compiler) { this.compiler = compiler; this.astFactory = compiler.createAstFactory(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isClass()) { if (NodeUtil.getEs6ClassConstructorMemberFunctionDef(n) == null) { addSyntheticConstructor(t, n); } } else if (n.isSuper()) { visitSuper(n, parent); } } private void addSyntheticConstructor(NodeTraversal t, Node classNode) { Node superClass = classNode.getSecondChild(); Node classMembers = classNode.getLastChild(); Node memberDef; if (superClass.isEmpty()) { // use the pre-cast type because createEmptyFunction expects a FunctionType Node function = astFactory.createEmptyFunction(getTypeBeforeCast(classNode)); compiler.reportChangeToChangeScope(function); memberDef = astFactory.createMemberFunctionDef("constructor", function); } else { if (!superClass.isQualifiedName()) { // This will be reported as an error in Es6ToEs3Converter. return; } Node body = IR.block(); // If a class is defined in an externs file or as an interface, it's only a stub, not an // implementation that should be instantiated. // A call to super() shouldn't actually exist for these cases and is problematic to // transpile, so don't generate it. if (!classNode.isFromExterns() && !isInterface(classNode)) { // Generate required call to super() // `super(...arguments);` // Note that transpilation of spread args must occur after this pass for this to work. Node exprResult = IR.exprResult( astFactory.createConstructorCall( classNode.getJSType(), // returned type is the subclass IR.superNode().setJSType(superClass.getJSType()), IR.iterSpread(astFactory.createArgumentsReference()))); body.addChildToFront(exprResult); NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.SUPER, compiler); NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.SPREAD_EXPRESSIONS, compiler); } Node constructor = astFactory.createFunction("", IR.paramList(), body, classNode.getJSType()); memberDef = astFactory.createMemberFunctionDef("constructor", constructor); } memberDef.useSourceInfoIfMissingFromForTree(classNode); memberDef.makeNonIndexableRecursive(); classMembers.addChildToFront(memberDef); NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.MEMBER_DECLARATIONS, compiler); // report newly created constructor compiler.reportChangeToChangeScope(memberDef.getOnlyChild()); // report change to scope containing the class compiler.reportChangeToEnclosingScope(memberDef); } /** Returns the node's pre-CAST type, if is has one. Otherwise just returns node.getJSType() */ private static JSType getTypeBeforeCast(Node node) { JSType typeBeforeCast = node.getJSTypeBeforeCast(); // might still return null if node.getJSType() is null (i.e. typechecking hasn't run) return typeBeforeCast != null ? typeBeforeCast : node.getJSType(); } private boolean isInterface(Node classNode) { JSDocInfo classJsDocInfo = NodeUtil.getBestJSDocInfo(classNode); return classJsDocInfo != null && classJsDocInfo.isInterface(); } private void visitSuper(Node node, Node parent) { checkState(node.isSuper()); Node exprRoot = node; if (exprRoot.getParent().isGetElem() || exprRoot.getParent().isGetProp()) { exprRoot = exprRoot.getParent(); } Node enclosingMemberDef = NodeUtil.getEnclosingNode( exprRoot, (Node n) -> { switch (n.getToken()) { case MEMBER_FUNCTION_DEF: case GETTER_DEF: case SETTER_DEF: case COMPUTED_PROP: return true; default: return false; } }); if (parent.isCall()) { // super(...) visitSuperCall(node, parent, enclosingMemberDef); } else if (parent.isGetProp() || parent.isGetElem()) { if (parent.getFirstChild() == node) { if (parent.getParent().isCall() && NodeUtil.isInvocationTarget(parent)) { // super.something(...) or super['something'](..) visitSuperPropertyCall(node, parent, enclosingMemberDef); } else { // super.something or super['something'] visitSuperPropertyAccess(node, parent, enclosingMemberDef); } } else { // super.something used in some other way compiler.report( JSError.make( node, CANNOT_CONVERT_YET, "Only calls to super or to a method of super are supported.")); } } else if (parent.isNew()) { throw new IllegalStateException("This should never happen. Did Es6SuperCheck fail to run?"); } else { // some other use of super we don't support yet compiler.report( JSError.make( node, CANNOT_CONVERT_YET, "Only calls to super or to a method of super are supported.")); } } private void visitSuperCall(Node node, Node parent, Node enclosingMemberDef) { checkState(parent.isCall(), parent); checkState(node.isSuper(), node); Node clazz = NodeUtil.getEnclosingClass(node); Node superName = clazz.getSecondChild(); if (!superName.isQualifiedName()) { // This will be reported as an error in Es6ToEs3Converter. return; } if (NodeUtil.isEs6ConstructorMemberFunctionDef(enclosingMemberDef)) { // Calls to super() constructors will be transpiled by Es6ConvertSuperConstructorCalls later. if (node.isFromExterns() || isInterface(clazz)) { // If a class is defined in an externs file or as an interface, it's only a stub, not an // implementation that should be instantiated. // A call to super() shouldn't actually exist for these cases and is problematic to // transpile, so just drop it. Node enclosingStatement = NodeUtil.getEnclosingStatement(node); Node enclosingStatementParent = enclosingStatement.getParent(); enclosingStatement.detach(); compiler.reportChangeToEnclosingScope(enclosingStatementParent); } // Calls to super() constructors will be transpiled by Es6ConvertSuperConstructorCalls // later. return; } else { // super can only be directly called in a constructor throw new IllegalStateException("This should never happen. Did Es6SuperCheck fail to run?"); } } private void visitSuperPropertyCall(Node node, Node parent, Node enclosingMemberDef) { checkState(parent.isGetProp() || parent.isGetElem(), parent); checkState(node.isSuper(), node); Node grandparent = parent.getParent(); checkState(grandparent.isCall()); Node clazz = NodeUtil.getEnclosingClass(node); Node superName = clazz.getSecondChild(); if (!superName.isQualifiedName()) { // This will be reported as an error in Es6ToEs3Converter. return; } Node callTarget = parent; Node callNode = IR.string("call"); callNode.makeNonIndexable(); if (enclosingMemberDef.isStaticMember()) { Node expandedSuper = superName.cloneTree().useSourceInfoFromForTree(node); expandedSuper.setOriginalName("super"); callTarget.replaceChild(node, expandedSuper); callTarget = astFactory.createGetProp(callTarget.detach(), "call"); grandparent.addChildToFront(callTarget); Node thisNode = astFactory.createThis(clazz.getJSType()); thisNode.makeNonIndexable(); // no direct correlation with original source grandparent.addChildAfter(thisNode, callTarget); grandparent.useSourceInfoIfMissingFromForTree(parent); } else { // Replace super node to give // super.method(...) -> SuperClass.prototype.method(...) Node expandedSuper = astFactory .createGetProp(superName.cloneTree(), "prototype") .useSourceInfoFromForTree(node); expandedSuper.setOriginalName("super"); node.replaceWith(expandedSuper); // Set the 'this' object correctly for the call // SuperClass.prototype.method(...) -> SuperClass.prototype.method.call(this, ...) callTarget = astFactory.createGetProp(callTarget.detach(), "call"); grandparent.addChildToFront(callTarget); JSType thisType = getInstanceTypeForClassNode(clazz); Node thisNode = astFactory.createThis(thisType); thisNode.makeNonIndexable(); // no direct correlation with original source grandparent.addChildAfter(thisNode, callTarget); grandparent.putBooleanProp(Node.FREE_CALL, false); grandparent.useSourceInfoIfMissingFromForTree(parent); } compiler.reportChangeToEnclosingScope(grandparent); } private JSType getInstanceTypeForClassNode(Node classNode) { checkArgument(classNode.isClass(), classNode); final JSType constructorType = classNode.getJSType(); final JSType result; if (constructorType != null) { checkArgument(constructorType.isConstructor(), classNode); result = JSType.toMaybeFunctionType(constructorType).getInstanceType(); } else { result = null; } return result; } private void visitSuperPropertyAccess(Node node, Node parent, Node enclosingMemberDef) { checkState(parent.isGetProp() || parent.isGetElem(), parent); checkState(node.isSuper(), node); Node grandparent = parent.getParent(); if (NodeUtil.isLValue(parent)) { // We don't support assigning to a super property compiler.report(JSError.make(parent, CANNOT_CONVERT_YET, "assigning to a super property")); return; } Node clazz = NodeUtil.getEnclosingClass(node); Node superName = clazz.getSecondChild(); if (!superName.isQualifiedName()) { // This will be reported as an error in Es6ToEs3Converter. return; } if (enclosingMemberDef.isStaticMember()) { // super.prop -> SuperClass.prop Node expandedSuper = superName.cloneTree().useSourceInfoFromForTree(node); expandedSuper.setOriginalName("super"); node.replaceWith(expandedSuper); } else { if (astFactory.isAddingTypes()) { // super.prop -> SuperClass.prototype.prop Node newprop = astFactory .createGetProp(superName.cloneTree(), "prototype") .useSourceInfoFromForTree(node); newprop.setOriginalName("super"); node.replaceWith(newprop); } else { String newPropName = Joiner.on('.').join(superName.getQualifiedName(), "prototype"); // TODO(bradfordcsmith): This is required for Kythe, which doesn't work correctly with // Node#useSourceInfoIfMissingFromForTree. Fortunately, we only care about Kythe // if we're not adding types. // Once this pass is always run after type checking, we can eliminate this branch. node.replaceWith( NodeUtil.newQName(compiler, newPropName, node, "super").useSourceInfoFromForTree(node)); } } compiler.reportChangeToEnclosingScope(grandparent); } @Override public void process(Node externs, Node root) { // Might need to synthesize constructors for ambient classes in .d.ts externs TranspilationPasses.processTranspile(compiler, externs, transpiledFeatures, this); TranspilationPasses.processTranspile(compiler, root, transpiledFeatures, this); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, transpiledFeatures, this); TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, transpiledFeatures); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy