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

com.google.javascript.jscomp.Es6RewriteClass 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.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.Es6ToEs3Util.cannotConvert;

import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
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 com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;

/**
 * Converts ES6 classes to valid ES5 or ES3 code.
 */
public final class Es6RewriteClass implements NodeTraversal.Callback, HotSwapCompilerPass {
  private static final FeatureSet features =
      FeatureSet.BARE_MINIMUM.with(
          Feature.CLASSES,
          Feature.CLASS_EXTENDS,
          Feature.CLASS_GETTER_SETTER,
          Feature.NEW_TARGET,
          Feature.SUPER);

  static final DiagnosticType DYNAMIC_EXTENDS_TYPE = DiagnosticType.error(
      "JSC_DYNAMIC_EXTENDS_TYPE",
      "The class in an extends clause must be a qualified name.");

  static final DiagnosticType CLASS_REASSIGNMENT = DiagnosticType.error(
      "CLASS_REASSIGNMENT",
      "Class names defined inside a function cannot be reassigned.");

  // This function is defined in js/es6/util/inherits.js
  static final String INHERITS = "$jscomp.inherits";

  private final AbstractCompiler compiler;
  private final AstFactory astFactory;
  private final JSType objectPropertyDescriptorType;
  private final Es6ConvertSuperConstructorCalls convertSuperConstructorCalls;

  public Es6RewriteClass(AbstractCompiler compiler) {
    this.compiler = compiler;
    JSTypeRegistry registry = compiler.getTypeRegistry();
    this.astFactory = compiler.createAstFactory();

    // Finds the type for `ObjectPropertyDescriptor`. Fallback to the unknown type if it's not
    // present, which may happen if typechecking hasn't run or this is a unit test w/o externs.
    JSType actualObjectPropertyDescriptorType = registry.getGlobalType("ObjectPropertyDescriptor");
    this.objectPropertyDescriptorType =
        actualObjectPropertyDescriptorType != null
            ? actualObjectPropertyDescriptorType
            : registry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
    convertSuperConstructorCalls = new Es6ConvertSuperConstructorCalls(compiler);
  }

  @Override
  public void process(Node externs, Node root) {
    TranspilationPasses.processTranspile(compiler, externs, features, this);
    TranspilationPasses.processTranspile(compiler, root, features, this);
    // Super constructor calls are done all at once as a separate step largely for historical
    // reasons. It used to be an entirely separate pass, but that has been fixed so we no longer
    // have an invalid AST state between passes.
    // TODO(bradfordcsmith): It would probably be more readable and efficient to merge the super
    //     constructor rewriting logic into this class.
    convertSuperConstructorCalls.setGlobalNamespace(new GlobalNamespace(compiler, externs, root));
    TranspilationPasses.processTranspile(compiler, externs, features, convertSuperConstructorCalls);
    TranspilationPasses.processTranspile(compiler, root, features, convertSuperConstructorCalls);
    TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, features);
  }

  @Override
  public void hotSwapScript(Node scriptRoot, Node originalRoot) {
    TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, features, this);
    TranspilationPasses.hotSwapTranspile(
        compiler, scriptRoot, features, convertSuperConstructorCalls);

    // Don't mark features as transpiled away if we had errors that prevented transpilation.
    // We don't want a redundant error from the AstValidator complaining that the features are still
    // there
    if (!compiler.hasHaltingErrors()) {
      TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, features);
    }
  }

  @Override
  public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
    switch (n.getToken()) {
      case GETTER_DEF:
      case SETTER_DEF:
        if (FeatureSet.ES3.contains(compiler.getOptions().getOutputFeatureSet())) {
          cannotConvert(compiler, n, "ES5 getters/setters (consider using --language_out=ES5)");
          return false;
        }
        break;
      default:
        break;
    }
    return true;
  }

  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    if (n.isClass()) {
      visitClass(t, n, parent);
    }
  }

  private void checkClassReassignment(Node clazz) {
    Node name = NodeUtil.getNameNode(clazz);
    Node enclosingFunction = NodeUtil.getEnclosingFunction(clazz);
    if (enclosingFunction == null) {
      return;
    }
    CheckClassAssignments checkAssigns = new CheckClassAssignments(name);
    NodeTraversal.traverse(compiler, enclosingFunction, checkAssigns);
  }

  /**
   * Classes are processed in 3 phases:
   * 
    *
  1. The class name is extracted. *
  2. Class members are processed and rewritten. *
  3. The constructor is built. *
*/ private void visitClass(final NodeTraversal t, final Node classNode, final Node parent) { checkClassReassignment(classNode); // Collect Metadata ClassDeclarationMetadata metadata = ClassDeclarationMetadata.create(classNode, parent, astFactory); if (metadata == null) { throw new IllegalStateException( "Can only convert classes that are declarations or the right hand" + " side of a simple assignment: " + classNode); } if (metadata.hasSuperClass() && !metadata.getSuperClassNameNode().isQualifiedName()) { compiler.report(JSError.make(metadata.getSuperClassNameNode(), DYNAMIC_EXTENDS_TYPE)); return; } Preconditions.checkState( NodeUtil.isStatement(metadata.getInsertionPoint().getNode()), "insertion point must be a statement: %s", metadata.getInsertionPoint().getNode()); Node constructor = null; // Process all members of the class Node classMembers = classNode.getLastChild(); for (Node member : classMembers.children()) { if ((member.isComputedProp() && (member.getBooleanProp(Node.COMPUTED_PROP_GETTER) || member.getBooleanProp(Node.COMPUTED_PROP_SETTER))) || (member.isGetterDef() || member.isSetterDef())) { visitNonMethodMember(member, metadata, t.getScope()); } else if (NodeUtil.isEs6ConstructorMemberFunctionDef(member)) { constructor = member.removeFirstChild().setJSType(classNode.getJSType()); constructor.setJSTypeBeforeCast(classNode.getJSTypeBeforeCast()); if (!metadata.isAnonymous()) { // Turns class Foo { constructor: function() {} } into function Foo() {}, // i.e. attaches the name to the ctor function. constructor.replaceChild( constructor.getFirstChild(), metadata.getClassNameNode().cloneNode()); } } else if (member.isEmpty()) { // Do nothing. } else { Preconditions.checkState(member.isMemberFunctionDef() || member.isComputedProp(), "Unexpected class member:", member); Preconditions.checkState(!member.getBooleanProp(Node.COMPUTED_PROP_VARIABLE), "Member variables should have been transpiled earlier:", member); visitMethod(member, metadata); } } checkNotNull( constructor, "Es6RewriteClasses expects all classes to have (possibly synthetic) constructors"); if (metadata.getDefinePropertiesObjForPrototype().hasChildren()) { Node definePropsCall = IR.exprResult( astFactory.createCall( createObjectDotDefineProperties(t.getScope()), metadata.getClassPrototypeNode().cloneTree(), metadata.getDefinePropertiesObjForPrototype())); definePropsCall.useSourceInfoIfMissingFromForTree(classNode); metadata.insertNodeAndAdvance(definePropsCall); } if (metadata.getDefinePropertiesObjForClass().hasChildren()) { Node definePropsCall = IR.exprResult( astFactory.createCall( createObjectDotDefineProperties(t.getScope()), metadata.getFullClassNameNode().cloneTree(), metadata.getDefinePropertiesObjForClass())); definePropsCall.useSourceInfoIfMissingFromForTree(classNode); metadata.insertNodeAndAdvance(definePropsCall); } JSDocInfo classJSDoc = NodeUtil.getBestJSDocInfo(classNode); JSDocInfoBuilder newInfo = JSDocInfoBuilder.maybeCopyFrom(classJSDoc); newInfo.recordConstructor(); Node enclosingStatement = NodeUtil.getEnclosingStatement(classNode); if (metadata.hasSuperClass()) { String superClassString = metadata.getSuperClassNameNode().getQualifiedName(); // Continue to add @extends to prevent an ExternsExportsPass error if (newInfo.isInterfaceRecorded()) { newInfo.recordExtendedInterface( new JSTypeExpression( new Node(Token.BANG, IR.string(superClassString)) .srcrefTree(metadata.getSuperClassNameNode()), metadata.getSuperClassNameNode().getSourceFileName())); } else { newInfo.recordBaseType( new JSTypeExpression( new Node(Token.BANG, IR.string(superClassString)) .srcrefTree(metadata.getSuperClassNameNode()), metadata.getSuperClassNameNode().getSourceFileName())); } if (!classNode.isFromExterns()) { Node inheritsCall = IR.exprResult( astFactory.createCall( astFactory.createQName(t.getScope(), "$jscomp.inherits"), metadata.getFullClassNameNode().cloneTree(), metadata.getSuperClassNameNode().cloneTree())) .useSourceInfoIfMissingFromForTree(metadata.getSuperClassNameNode()); enclosingStatement.getParent().addChildAfter(inheritsCall, enclosingStatement); } } addTypeDeclarations(t.getScope(), metadata, enclosingStatement); if (NodeUtil.isStatement(classNode)) { constructor.getFirstChild().setString(""); Node ctorVar = IR.let(metadata.getClassNameNode().cloneNode(), constructor); ctorVar.useSourceInfoIfMissingFromForTree(classNode); parent.replaceChild(classNode, ctorVar); NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.LET_DECLARATIONS, compiler); } else { parent.replaceChild(classNode, constructor); } NodeUtil.markFunctionsDeleted(classNode, compiler); if (NodeUtil.isStatement(constructor)) { constructor.setJSDocInfo(newInfo.build()); } else if (parent.isName()) { // The constructor function is the RHS of a var statement. // Add the JSDoc to the VAR node. Node var = parent.getParent(); var.setJSDocInfo(newInfo.build()); } else if (constructor.getParent().isName()) { // Is a newly created VAR node. Node var = constructor.getGrandparent(); var.setJSDocInfo(newInfo.build()); } else if (parent.isAssign()) { // The constructor function is the RHS of an assignment. // Add the JSDoc to the ASSIGN node. parent.setJSDocInfo(newInfo.build()); } else { throw new IllegalStateException("Unexpected parent node " + parent); } FunctionType classType = JSType.toMaybeFunctionType(classNode.getJSType()); if (classType != null) { // classNode is no longer in the AST, so we need to update the reference to it that is // stored in the class's type, if any. classType.setSource(constructor); } t.reportCodeChange(); } private Node createObjectDotDefineProperties(Scope scope) { return astFactory.createQName(scope, "$jscomp.global.Object.defineProperties"); } private Node createObjectDotDefineProperty(Scope scope) { return astFactory.createQName(scope, "$jscomp.global.Object.defineProperty"); } /** @param member A getter or setter */ private void addToDefinePropertiesObject(ClassDeclarationMetadata metadata, Node member) { Preconditions.checkArgument(!member.isComputedProp()); Node obj = member.isStaticMember() ? metadata.getDefinePropertiesObjForClass() : metadata.getDefinePropertiesObjForPrototype(); Node prop = NodeUtil.getFirstPropMatchingKey(obj, member.getString()); if (prop == null) { prop = createPropertyDescriptor(); Node stringKey = astFactory.createStringKey(member.getString(), prop); if (member.isQuotedString()) { stringKey.putBooleanProp(Node.QUOTED_PROP, true); } obj.addChildToBack(stringKey); } Node function = member.getLastChild(); JSDocInfo info = NodeUtil.getBestJSDocInfo(function); Node stringKey = astFactory.createStringKey(member.isGetterDef() ? "get" : "set", function.detach()); stringKey.setJSDocInfo(info); prop.addChildToBack(stringKey); prop.useSourceInfoIfMissingFromForTree(member); } /** Appends an Object.defineProperty call defining the given computed getter or setter */ private void extractComputedProperty( Node computedMember, ClassDeclarationMetadata metadata, Scope scope) { Node owner = computedMember.isStaticMember() ? metadata.getFullClassNameNode() : metadata.getClassPrototypeNode(); Node property = computedMember.removeFirstChild(); Node propertyValue = computedMember.removeFirstChild(); Node propertyDescriptor = createPropertyDescriptor(); Node stringKey = astFactory.createStringKey( computedMember.getBooleanProp(Node.COMPUTED_PROP_GETTER) ? "get" : "set", propertyValue); propertyDescriptor.addChildToBack(stringKey); Node objectDefinePropertyCall = astFactory.createCall( createObjectDotDefineProperty(scope), owner.cloneTree(), property, propertyDescriptor); metadata.insertNodeAndAdvance( IR.exprResult(objectDefinePropertyCall).useSourceInfoIfMissingFromForTree(computedMember)); } /** * Visits getters and setters, including both static and instance properties, and computed and * non-computed properties. * *

Non-computed getters and setters are aggregated into two Object.defineProperties calls. One * defines static members and the other instance members. This is just an optimization; it's just * as sound to append a single Object.defineProperty definition for each here. * *

Computed getters and setters are defined in individual Object.defineProperty calls */ private void visitNonMethodMember(Node member, ClassDeclarationMetadata metadata, Scope scope) { if (member.isComputedProp()) { extractComputedProperty(member.detach(), metadata, scope); return; } addToDefinePropertiesObject(metadata, member); if (!member.isStaticMember()) { return; } checkState(!member.isComputedProp(), member); // Add stub declarations of static properties so that they are not broken by property collapsing Map membersToDeclare = metadata.getClassMembersToDeclare(); ClassProperty.Builder builder = ClassProperty.builder(); String memberName = member.getString(); if (member.isQuotedString()) { builder.kind(ClassProperty.PropertyKind.QUOTED_PROPERTY); } else { builder.kind(ClassProperty.PropertyKind.NORMAL_PROPERTY); } builder.propertyKey(memberName); JSDocInfoBuilder jsDoc = new JSDocInfoBuilder(false); jsDoc.recordNoCollapse(); builder.jsDocInfo(jsDoc.build()); membersToDeclare.put(memberName, builder.build()); } /** * Handles transpilation of a standard class member function. Getters, setters, and the * constructor are not handled here. */ private void visitMethod(Node member, ClassDeclarationMetadata metadata) { Node qualifiedMemberAccess = getQualifiedMemberAccess(member, metadata); Node method = member.getLastChild().detach(); // Use the source info from the method (a FUNCTION) not the MEMBER_FUNCTION_DEF // because the MEMBER_FUNCTION_DEf source info only corresponds to the identifier Node assign = astFactory.createAssign(qualifiedMemberAccess, method).useSourceInfoIfMissingFrom(method); JSDocInfo info = member.getJSDocInfo(); if (member.isStaticMember() && NodeUtil.referencesOwnReceiver(assign.getLastChild())) { JSDocInfoBuilder memberDoc = JSDocInfoBuilder.maybeCopyFrom(info); // adding an @this type prevents a JSC_UNSAFE_THIS error later on. memberDoc.recordThisType( new JSTypeExpression( new Node(Token.BANG, new Node(Token.QMARK)).srcrefTree(member), member.getSourceFileName())); info = memberDoc.build(); } if (info != null) { assign.setJSDocInfo(info); } Node newNode = NodeUtil.newExpr(assign); metadata.insertNodeAndAdvance(newNode); } /** * Adds declarations for static properties defined with a getter or setter, so that optimizations * like property collapsing recognize the properties' existence. */ private void addTypeDeclarations( Scope scope, ClassDeclarationMetadata metadata, Node insertionPoint) { for (ClassProperty property : metadata.getClassMembersToDeclare().values()) { Node declaration = property.getDeclaration(astFactory, scope, metadata.getFullClassNameNode().cloneTree()); declaration.useSourceInfoIfMissingFromForTree(metadata.getClassNameNode()); insertionPoint.getParent().addChildAfter(declaration, insertionPoint); insertionPoint = declaration; } } /** * Constructs a Node that represents an access to the given class member, qualified by either the * static or the instance access context, depending on whether the member is static. * *

WARNING: {@code member} may be modified/destroyed by this method, do not use it * afterwards. */ private Node getQualifiedMemberAccess(Node member, ClassDeclarationMetadata metadata) { Node context = member.isStaticMember() ? metadata.getFullClassNameNode().cloneTree() : metadata.getClassPrototypeNode().cloneTree(); context.makeNonIndexableRecursive(); if (member.isComputedProp()) { return astFactory .createGetElem(context, member.removeFirstChild()) .useSourceInfoIfMissingFromForTree(member); } else { Node methodName = member.getFirstFirstChild(); return astFactory .createGetProp(context, member.getString()) .useSourceInfoFromForTree(methodName); } } private class CheckClassAssignments extends NodeTraversal.AbstractPostOrderCallback { private final Node className; public CheckClassAssignments(Node className) { this.className = className; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isAssign() || n.getFirstChild() == className) { return; } if (className.matchesQualifiedName(n.getFirstChild())) { compiler.report(JSError.make(n, CLASS_REASSIGNMENT)); } } } private Node createPropertyDescriptor() { return IR.objectlit( astFactory.createStringKey("configurable", astFactory.createBoolean(true)), astFactory.createStringKey("enumerable", astFactory.createBoolean(true))) .setJSType(objectPropertyDescriptorType); } @AutoValue abstract static class ClassProperty { enum PropertyKind { /** * Any kind of quoted property, which can include numeric properties that we treated as * quoted. * *

       * class Example {
       *   'quoted'() {}
       *   42() { return 'the answer'; }
       * }
       * 
*/ QUOTED_PROPERTY, /** * A computed property, e.g. using bracket [] access. * *

Computed properties *must* currently be qualified names, and not literals, function * calls, etc. * *

       * class Example {
       *   [variable]() {}
       *   [another.example]() {}
       * }
       * 
*/ COMPUTED_PROPERTY, /** * A normal property definition. * *
       * class Example {
       *   normal() {}
       * }
       * 
*/ NORMAL_PROPERTY, } /** * The name of this ClassProperty for NORMAL_PROPERTY, the string value of this property if * QUOTED_PROPERTY, or the qualified name of the computed property. */ abstract String propertyKey(); abstract PropertyKind kind(); abstract JSDocInfo jsDocInfo(); /** * Returns an EXPR_RESULT node that declares this property on the given node. * *

Examples: * *

     *   /** @type {string} *\/
     *   Class.prototype.property;
     *
     *   /** @type {string} *\/
     *   Class.staticProperty;
     * 
* * @param toDeclareOn the node to declare the property on. This should either be a reference to * the class (if a static property) or a class' prototype (if non-static). This should * always be a new node, as this method will insert it into the returned EXPR_RESULT. */ final Node getDeclaration(AstFactory astFactory, Scope scope, Node toDeclareOn) { Node decl = null; switch (kind()) { case QUOTED_PROPERTY: decl = astFactory.createGetElem(toDeclareOn, astFactory.createString(propertyKey())); break; case COMPUTED_PROPERTY: // Note that at the moment we only allow qualified names as the keys for class computed // properties. // TODO(bradfordcsmith): Clone the original declared member qualified name instead of // creating it from scratch from the string form. That way we would just reuse the type // information, instead of AstFactory having to re-create it. decl = astFactory.createGetElem(toDeclareOn, astFactory.createQName(scope, propertyKey())); break; case NORMAL_PROPERTY: decl = astFactory.createGetProp(toDeclareOn, propertyKey()); break; } decl.setJSDocInfo(jsDocInfo()); decl = astFactory.exprResult(decl); return decl; } static Builder builder() { return new AutoValue_Es6RewriteClass_ClassProperty.Builder(); } @AutoValue.Builder abstract static class Builder { abstract Builder propertyKey(String value); abstract Builder kind(PropertyKind value); abstract Builder jsDocInfo(JSDocInfo value); abstract ClassProperty build(); } } /** * Represents static metadata on a class declaration expression - i.e. the qualified name that a * class declares (directly or by assignment), whether it's anonymous, and where transpiled code * should be inserted (i.e. which object will hold the prototype after transpilation). * *

Note that this class is NOT deeply immutable! Don't use it in a Map. The AutoValue(.Builder) * is just used to simplify creating instances. */ @AutoValue abstract static class ClassDeclarationMetadata { /** A statement node. Transpiled methods etc of the class are inserted after this node. */ abstract InsertionPoint getInsertionPoint(); /** * An object literal node that will be used in a call to Object.defineProperties, to add getters * and setters to the prototype. */ abstract Node getDefinePropertiesObjForPrototype(); /** * An object literal node that will be used in a call to Object.defineProperties, to add getters * and setters to the class. */ abstract Node getDefinePropertiesObjForClass(); // Property declarations to be added to the class abstract Map getClassMembersToDeclare(); /** * The fully qualified name of the class, as a cloneable node. May come from the class itself or * the LHS of an assignment. */ abstract Node getFullClassNameNode(); /** * The fully qualified name of this class, plus ".prototype", as a cloneable node with type * information as needed. */ abstract Node getClassPrototypeNode(); /** Whether the constructor function in the output should be anonymous. */ abstract boolean isAnonymous(); abstract Node getClassNameNode(); abstract Node getSuperClassNameNode(); @AutoValue.Builder abstract static class Builder { abstract Builder setInsertionPoint(InsertionPoint insertionPoint); abstract Builder setFullClassNameNode(Node fullClassNameNode); abstract Node getFullClassNameNode(); abstract Builder setClassMembersToDeclare(Map classMembersToDeclare); abstract Builder setAnonymous(boolean anonymous); abstract Builder setClassNameNode(Node classNameNode); abstract Builder setSuperClassNameNode(Node superClassNameNode); abstract Builder setClassPrototypeNode(Node node); abstract Builder setDefinePropertiesObjForClass(Node node); abstract Builder setDefinePropertiesObjForPrototype(Node node); abstract ClassDeclarationMetadata build(); } public static Builder builder() { return new AutoValue_Es6RewriteClass_ClassDeclarationMetadata.Builder() .setClassMembersToDeclare(new LinkedHashMap<>()); } /** * Creates an instance for a class statement or a class expression in a simple assignment or var * statement with a qualified name. In any other case, returns null. */ @Nullable static ClassDeclarationMetadata create(Node classNode, Node parent) { return create(classNode, parent, AstFactory.createFactoryWithoutTypes()); } private static ClassDeclarationMetadata create( Node classNode, Node parent, AstFactory astFactory) { Node classNameNode = classNode.getFirstChild(); Node superClassNameNode = classNameNode.getNext(); Builder builder = ClassDeclarationMetadata.builder() .setSuperClassNameNode(superClassNameNode) .setClassNameNode(classNameNode); // If this is a class statement, or a class expression in a simple // assignment or var statement, convert it. In any other case, the // code is too dynamic, so return null. if (NodeUtil.isClassDeclaration(classNode)) { builder .setInsertionPoint(InsertionPoint.from(classNode)) .setFullClassNameNode(classNameNode) .setAnonymous(false); } else if (parent.isAssign() && parent.getParent().isExprResult()) { // Add members after the EXPR_RESULT node: // example.C = class {}; example.C.prototype.foo = function() {}; Node fullClassNameNode = parent.getFirstChild(); if (!fullClassNameNode.isQualifiedName()) { return null; } builder .setInsertionPoint(InsertionPoint.from(parent.getParent())) .setFullClassNameNode(fullClassNameNode) .setAnonymous(true); } else if (parent.isExport()) { builder .setInsertionPoint(InsertionPoint.from(classNode)) .setFullClassNameNode(classNameNode) .setAnonymous(false); } else if (parent.isName()) { // Add members after the 'var' statement. // var C = class {}; C.prototype.foo = function() {}; builder .setInsertionPoint(InsertionPoint.from(parent.getParent())) .setFullClassNameNode(parent.cloneNode()) // specifically don't want children .setAnonymous(true); } else { // Cannot handle this class declaration. return null; } // TODO(sdh): are these types safe? JSType classType = builder.getFullClassNameNode().getJSType(); builder.setClassPrototypeNode( astFactory.createGetProp(builder.getFullClassNameNode().cloneTree(), "prototype")); builder.setDefinePropertiesObjForClass(IR.objectlit().setJSType(classType)); builder.setDefinePropertiesObjForPrototype(IR.objectlit().setJSType(classType)); return builder.build(); } void insertNodeAndAdvance(Node newNode) { getInsertionPoint().insertNodeAndAdvance(newNode); } boolean hasSuperClass() { return !getSuperClassNameNode().isEmpty(); } } /** * Used by ClassDeclarationMetadata to represent the point where we insert the next transpiled * method of a class */ static class InsertionPoint { private Node insertionPoint; private InsertionPoint(Node insertionPoint) { this.insertionPoint = insertionPoint; } void insertNodeAndAdvance(Node newNode) { insertionPoint.getParent().addChildAfter(newNode, insertionPoint); insertionPoint = newNode; } static InsertionPoint from(Node start) { return new InsertionPoint(start); } Node getNode() { return insertionPoint; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy