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

com.google.javascript.jscomp.Es6ConvertSuperConstructorCalls 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.

The 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.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.AstFactory.type;

import com.google.common.collect.Iterables;
import com.google.javascript.jscomp.GlobalNamespace.Name;
import com.google.javascript.jscomp.GlobalNamespace.Ref;
import com.google.javascript.jscomp.colors.Color;
import com.google.javascript.jscomp.colors.StandardColors;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.QualifiedName;
import com.google.javascript.rhino.StaticScope;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import org.jspecify.nullness.Nullable;

/** Converts {@code super()} calls. */
public final class Es6ConvertSuperConstructorCalls implements NodeTraversal.Callback {
  private static final String TMP_ERROR = "$jscomp$tmp$error";
  private static final String SUPER_THIS = "$jscomp$super$this";
  private static final QualifiedName JSCOMP_INHERITS = QualifiedName.of("$jscomp.inherits");

  /** Stores superCalls for a constructor. */
  private static final class ConstructorData {
    final Node constructor;
    final List superCalls;

    ConstructorData(Node constructor) {
      this.constructor = constructor;
      superCalls = new ArrayList<>();
    }
  }

  private final AbstractCompiler compiler;
  private final Deque constructorDataStack;
  private final AstFactory astFactory;
  // Note: this GlobalNamespace needs to be the namespace we get after the transpilation passes run.
  // meaning compiler.getTranspilationNamespace() does not work.
  private GlobalNamespace globalNamespace;
  private final StaticScope transpilationNamespace;

  public Es6ConvertSuperConstructorCalls(AbstractCompiler compiler) {
    this.compiler = compiler;
    this.astFactory = compiler.createAstFactory();
    this.transpilationNamespace = compiler.getTranspilationNamespace();
    this.constructorDataStack = new ArrayDeque<>();
  }

  @Override
  public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
    if (n.isFunction()) {
      // TODO(bradfordcsmith): Avoid creating data for non-constructor functions.
      constructorDataStack.push(new ConstructorData(n));
    } else if (n.isSuper()) {
      // super(args) or super.prop
      checkState(n.isFirstChildOf(parent), parent);
      if (parent.isGetProp()) {
        // TODO(bradfordcsmith): `super.prop` should have been removed before this code executes,
        //     but instead is being left untranspiled when there's no `extends` clause, so
        //     we have to report that problem here.
        t.report(n, TranspilationUtil.CANNOT_CONVERT_YET, "super access with no extends clause");
        return false;
      }
      // must be super(args)
      checkState(parent.isCall(), parent);
      ConstructorData constructorData = checkNotNull(constructorDataStack.peek());
      constructorData.superCalls.add(parent);
    }
    return true;
  }

  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    ConstructorData constructorData = constructorDataStack.peek();
    if (constructorData != null && n == constructorData.constructor) {
      constructorDataStack.pop();
      visitSuper(t, constructorData);
    }
  }

  private void visitSuper(NodeTraversal t, ConstructorData constructorData) {
    // NOTE: When this pass runs:
    // -   ES6 classes have already been rewritten as ES5 functions.
    // -   All subclasses have $jscomp.inherits() calls connecting them to their parent class.
    // -   All instances of `super` that are not super constructor calls have been rewritten.
    Node constructor = constructorData.constructor;
    List superCalls = constructorData.superCalls;
    if (superCalls.isEmpty()) {
      return; // nothing to do
    }
    if (constructor.isFromExterns()) {
      // This class is defined in an externs file, so it's only a stub, not the actual
      // implementation that should be instantiated.
      // A call to super() shouldn't actually exist for a stub and is problematic to transpile,
      // so just drop it.
      for (Node superCall : superCalls) {
        Node enclosingStatement = NodeUtil.getEnclosingStatement(superCall);
        Node enclosingScope = enclosingStatement.getParent();
        enclosingStatement.detach();
        compiler.reportChangeToEnclosingScope(enclosingScope);
      }
    } else {
      // Find the `foo.SuperClass` part of `$jscomp.inherits(foo.SubClass, foo.SuperClass)`
      Node superClassNameNode = getSuperClassQNameNode(constructor);

      String superClassQName = superClassNameNode.getQualifiedName();
      AstFactory.Type thisType = getTypeOfThisForConstructor(constructorData.constructor);
      if (isNativeObjectClass(t, superClassQName)) {
        // There's no need to call Object as a super constructor, so just replace the call with
        // `this`, which is its correct return value.
        // TODO(bradfordcsmith): Although unlikely, super() could have argument expressions with
        //     side-effects.
        for (Node superCall : superCalls) {
          Node thisNode = astFactory.createThis(thisType).srcref(superCall);
          superCall.replaceWith(thisNode);
          compiler.reportChangeToEnclosingScope(thisNode);
        }
      } else if (isKnownNativeClass(t, superClassQName)) {
        // Although we're transpiling down to ES5, it's quite possible that the code will end up
        // running in an environment where native classes are ES6 classes.
        // To correctly extend them with the ES5 classes we're generating here, we must use
        // `$jscomp.construct`, which is our wrapper around `Reflect.construct`.
        convertSuperCallsToJsCompConstructCalls(
            constructor, superCalls, superClassNameNode, thisType);
      } else if (isNativeErrorClass(t, superClassQName)) {
        // TODO(bradfordcsmith): It might be better to use $jscomp.construct() for these instead
        // of our custom-made, Error-specific workaround.
        for (Node superCall : superCalls) {
          Node newSuperCall =
              createNewSuperCall(superClassNameNode, superCall, thisType, type(superCall));
          replaceNativeErrorSuperCall(superCall, newSuperCall);
        }
      } else if (isKnownToReturnOnlyUndefined(superClassQName)) {
        // super() will not change the value of `this`.
        for (Node superCall : superCalls) {
          Node newSuperCall =
              createNewSuperCall(
                  superClassNameNode, superCall, thisType, type(StandardColors.NULL_OR_VOID));
          Node superCallParent = superCall.getParent();
          if (superCallParent.hasOneChild() && NodeUtil.isStatement(superCallParent)) {
            // super() is a statement unto itself
            superCall.replaceWith(newSuperCall);
          } else {
            // super() is part of an expression, so it must return `this`.
            superCall.replaceWith(
                astFactory
                    .createComma(newSuperCall, astFactory.createThis(thisType))
                    .srcrefTreeIfMissing(superCall));
          }
          compiler.reportChangeToEnclosingScope(superCallParent);
        }
      } else {
        // Either the superclass constructor returns a value, or we cannot find its definition in
        // the sources, so we don't know if it does.
        //
        // 1. We must use the value it returns, if defined, as the 'this' value in the constructor
        //    we're currently transpiling, and we must also return it from this constructor.
        // 2. It may be an ES6 class defined outside of the sources we can see.
        //
        // The code below works as long as the class we're extending is an ES5 class, but will
        // break if we're extending an ES6 class (#2), because it calls the superclass constructor
        // without using `new` or `Reflect.construct()`
        // TODO(b/36789413): We should use $jscomp.construct() here to avoid breakage when extending
        // an ES6 class.
        Node constructorBody = checkNotNull(constructor.getChildAtIndex(2));
        Node firstStatement = constructorBody.getFirstChild();
        Node firstSuperCall = superCalls.get(0);

        if (constructorBody.hasOneChild()
            && firstStatement.isExprResult()
            && firstStatement.hasOneChild()
            && firstStatement.getFirstChild() == firstSuperCall) {
          checkState(superCalls.size() == 1, constructor);
          // Super call is the entire constructor, so just replace it with.
          // `return  || this;`
          Node newReturn =
              astFactory.createOr(
                  createNewSuperCall(
                      superClassNameNode,
                      superCalls.get(0),
                      type(superCalls.get(0)),
                      type(StandardColors.UNKNOWN)),
                  astFactory.createThis(thisType));
          firstStatement.replaceWith(IR.returnNode(newReturn).srcrefTreeIfMissing(firstStatement));
        } else {
          final AstFactory.Type typeOfThis = getTypeOfThisForConstructor(constructor);
          // `this` -> `$jscomp$super$this` throughout the constructor body,
          // except for super() calls.
          updateThisToSuperThis(typeOfThis, constructorBody, superCalls);
          // Start constructor with `var $jscomp$super$this;`
          constructorBody.addChildToFront(
              IR.var(astFactory.createName(SUPER_THIS, typeOfThis)).srcrefTree(constructorBody));
          // End constructor with `return $jscomp$super$this;`
          constructorBody.addChildToBack(
              IR.returnNode(astFactory.createName(SUPER_THIS, typeOfThis))
                  .srcrefTree(constructorBody));
          // Replace each super() call with `($jscomp$super$this =  || this)`
          for (Node superCall : superCalls) {
            Node newSuperCall =
                createNewSuperCall(
                    superClassNameNode, superCall, typeOfThis, type(StandardColors.UNKNOWN));
            superCall.replaceWith(
                astFactory
                    .createAssign(
                        astFactory.createName(SUPER_THIS, typeOfThis),
                        astFactory.createOr(newSuperCall, astFactory.createThis(typeOfThis)))
                    .srcrefTreeIfMissing(superCall));
          }
        }
        compiler.reportChangeToEnclosingScope(constructorBody);
      }
    }
  }

  /**
   * Change calls to `super` to use `$jscomp.construct` instead.
   *
   * 

   *   // note that conversion of the ES6 class to ES5 happens before this pass.
   *   // we're just cleaning up the super() calls now
   *   var Foo = function(arg1, arg2) {
   *     super(arg1);
   *     this.prop = arg2;
   *   }
   *   // becomes
   *   var Foo = function(arg1, arg2) {
   *     // tmp var and return are necessary, because $jscomp.construct() always creates a new
   *     // object to be used as `this`
   *     var $jscomp$super$this;
   *     $jscomp$super$this = $jscomp.construct(SuperClassName, [arg1], this.constructor);
   *     $jscomp$super$this.prop = arg2
   *     return $jscomp$super$this;
   *   }
   * 
*/ private void convertSuperCallsToJsCompConstructCalls( Node constructor, List superCalls, Node superClassNameNode, AstFactory.Type thisType) { Node constructorBody = checkNotNull(constructor.getChildAtIndex(2)); Node firstStatement = constructorBody.getFirstChild(); // A constructor body with no call to `super()` is a syntax error for a class that has an // extends clause. An error should have been reported and we should never reach this point // for an empty constructor body. checkNotNull(firstStatement, "Empty constructor body"); Node firstSuperCall = superCalls.get(0); if (constructorBody.hasOneChild() && firstStatement.isExprResult() && firstStatement.hasOneChild() && firstStatement.getFirstChild() == firstSuperCall) { checkState(superCalls.size() == 1, constructor); // Super call is the entire constructor, so just replace it with. // `return $jscomp.construct(SuperClassName, [args], this.constructor);` firstStatement.replaceWith( astFactory.createReturn( createJSCompConstructorCall(superClassNameNode, firstSuperCall, thisType))); } else { final AstFactory.Type typeOfThis = getTypeOfThisForConstructor(constructor); // `this` -> `$jscomp$super$this` throughout the constructor body, // except for super() calls. updateThisToSuperThis(typeOfThis, constructorBody, superCalls); // Start constructor with `var $jscomp$super$this;` constructorBody.addChildToFront( astFactory.createSingleVarNameDeclaration(SUPER_THIS).srcrefTree(constructorBody)); // End constructor with `return $jscomp$super$this;` constructorBody.addChildToBack( astFactory .createReturn(astFactory.createName(SUPER_THIS, typeOfThis)) .srcrefTree(constructorBody)); // Replace each super() call with `($jscomp$super$this = $jscomp.construct(...))` for (Node superCall : superCalls) { superCall.replaceWith( astFactory .createAssign( astFactory.createName(SUPER_THIS, typeOfThis).srcref(superCall), createJSCompConstructorCall(superClassNameNode, superCall, thisType)) .srcref(superCall)); } } compiler.reportChangeToEnclosingScope(constructorBody); } private boolean isKnownToReturnOnlyUndefined(String functionQName) { if (globalNamespace == null) { return false; } Name globalName = globalNamespace.getSlot(functionQName); if (globalName == null) { return false; } Ref declarationRef = globalName.getDeclaration(); if (declarationRef == null) { for (Ref ref : globalName.getRefs()) { if (ref.isSet()) { declarationRef = ref; } } } if (declarationRef == null) { return false; } Node declaredVarOrProp = declarationRef.getNode(); if (declaredVarOrProp.isFromExterns()) { return false; } Node declaration = declaredVarOrProp.getParent(); Node declaredValue = null; if (declaration.isFunction()) { declaredValue = declaration; } else if (NodeUtil.isNameDeclaration(declaration) && declaredVarOrProp.isName()) { if (declaredVarOrProp.hasChildren()) { declaredValue = checkNotNull(declaredVarOrProp.getFirstChild()); } else { return false; // Declaration without an assigned value. } } else if (declaration.isAssign() && declaration.getFirstChild() == declaredVarOrProp) { declaredValue = checkNotNull(declaration.getSecondChild()); } else if (declaration.isObjectLit() && declaredVarOrProp.hasOneChild()) { declaredValue = checkNotNull(declaredVarOrProp.getFirstChild()); } else { throw new IllegalStateException( "Unexpected declaration format:\n" + declaration.toStringTree()); } return isNodeKnownToOnlyReturnUndefined(declaredValue); } private boolean isNodeKnownToOnlyReturnUndefined(Node node) { if (node.isFunction()) { Node functionBody = checkNotNull(node.getChildAtIndex(2)); return !new UndefinedReturnValueCheck().mayReturnDefinedValue(functionBody); } else if (node.isQualifiedName()) { return isKnownToReturnOnlyUndefined(node.getQualifiedName()); } else if (node.isHook()) { // cond ? left : right; Node left = node.getSecondChild(); Node right = node.getLastChild(); return isNodeKnownToOnlyReturnUndefined(left) && isNodeKnownToOnlyReturnUndefined(right); } else { // TODO(bradfordcsmith): What cases are these? Can we do better? return false; } } private class UndefinedReturnValueCheck { private boolean foundNonEmptyReturn; boolean mayReturnDefinedValue(Node functionBody) { foundNonEmptyReturn = false; NodeTraversal.Callback checkForDefinedReturnValue = new NodeTraversal.AbstractShallowCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!foundNonEmptyReturn) { if (n.isReturn() && n.hasChildren() && !n.getFirstChild().matchesName("undefined")) { foundNonEmptyReturn = true; } } } }; NodeTraversal.traverse(compiler, functionBody, checkForDefinedReturnValue); return foundNonEmptyReturn; } } /** * Returns a transpiled version of the super constructor call. * *

The children of the passed in `superCall` are all removed from it by this method, but the * existing call itself is not replaced in the AST yet. The returned node is not yet attached to * the AST. */ private Node createNewSuperCall( Node superClassQNameNode, Node superCall, AstFactory.Type thisType, AstFactory.Type resultType) { checkArgument(superClassQNameNode.isQualifiedName(), superClassQNameNode); checkArgument(superCall.isCall(), superCall); Node callee = superCall.removeFirstChild(); checkState(callee.isSuper(), callee); List args = new ArrayList<>(); boolean hasSpreadArg = false; while (superCall.hasChildren()) { final Node arg = superCall.removeFirstChild(); hasSpreadArg = hasSpreadArg || arg.isSpread(); args.add(arg); } // Node to which args should be appended if (hasSpreadArg) { // We want to convert // // super(x, ...params, y) // to // Foo.apply(this, [x, ...params, y]) // // because, after transpilation of spread this becomes // // Foo.apply(this, [x, $jscomp.arrayFromIterable(params), y]) // // If we used `call`, we'd get this nonsense instead // // Foo.call.apply(Foo, [this, x, $jscomp.arrayFromIterable(params), y]) Node superClassDotApply = astFactory .createGetPropWithUnknownType(superClassQNameNode.cloneTree(), "apply") .srcrefTree(callee); // Create `SuperClass.call(this)` Node newSuperCall = astFactory.createCall(superClassDotApply, resultType).srcref(superCall); newSuperCall.addChildToBack(astFactory.createThis(thisType).srcref(callee)); newSuperCall.putBooleanProp(Node.FREE_CALL, false); // callee is now a getprop // It's very common to just have `super(...arguments)`, because we generate constructors // containing that for extending classes that don't have an explicit constructor. // For that case it's more efficient to just convert `super(...arguments)` to // `SuperClass.apply(this, arguments)` here rather than relying on later optimizations to // convert `[...arguments]` to `arguments`. if (isSingleSpreadOfArguments(args)) { newSuperCall.addChildToBack(Iterables.getOnlyElement(args).getOnlyChild().detach()); } else { newSuperCall.addChildToBack(astFactory.createArraylit(args).srcref(superCall)); } return newSuperCall; } else { // We want to convert // // super(arg1, arg2) // to // Foo.call(this, arg1, arg2) // // Using `call` is shorter than using `apply`. Node superClassDotCall = astFactory .createGetProp( superClassQNameNode.cloneTree(), "call", type(StandardColors.TOP_OBJECT)) .srcrefTree(callee); Node newSuperCall = astFactory.createCall(superClassDotCall, resultType).srcref(superCall); newSuperCall.addChildToBack(astFactory.createThis(thisType).srcref(callee)); newSuperCall.putBooleanProp(Node.FREE_CALL, false); // callee is now a getprop for (Node arg : args) { newSuperCall.addChildToBack(arg); } return newSuperCall; } } /** * Returns a transpiled version of the super constructor call using `$jscomp.constructor`. * *

The children of the passed in `superCall` are all removed from it by this method, but the * existing call itself is not replaced in the AST yet. The returned node is not yet attached to * the AST. */ private Node createJSCompConstructorCall( Node superClassQNameNode, Node superCall, AstFactory.Type thisType) { checkArgument(superClassQNameNode.isQualifiedName(), superClassQNameNode); checkArgument(superCall.isCall(), superCall); final Node callee = checkNotNull(superCall.removeFirstChild(), superCall); checkState(callee.isSuper(), callee); // `$jscomp.construct` final Node jscompDotConstruct = astFactory.createQName(this.transpilationNamespace, "$jscomp.construct").srcrefTree(callee); final Node superClassQName = superClassQNameNode.cloneTree(); // extract the arguments from the super() call and create the arguments list to pass to // $jscomp.construct() final List superCallArgList = new ArrayList<>(); while (superCall.hasChildren()) { superCallArgList.add(superCall.removeFirstChild()); } // It's very common to just have `super(...arguments)`, because we generate constructors // containing that for extending classes that don't have an explicit constructor. // For that case it's more efficient to just convert `super(...arguments)` to // `$jscomp.construct(SuperClass, arguments, this.constructor)` here rather than relying on // later optimizations to // convert `[...arguments]` to `arguments`. final Node superArgs = isSingleSpreadOfArguments(superCallArgList) // pull out `arguments` from `...arguments` ? Iterables.getOnlyElement(superCallArgList).getOnlyChild().detach() : astFactory.createArraylit(superCallArgList).srcref(superCall); // `this.constructor` final Node thisDotConstructor = astFactory .createGetProp( astFactory.createThis(thisType), "constructor", type(superClassQNameNode)) .srcrefTree(superCall); // `super(arg1, arg2)` // becomes // `$jscomp.construct(SuperClassName, [arg1, arg2], this.constructor)` return astFactory .createCall( jscompDotConstruct, type(superCall), superClassQName, superArgs, thisDotConstructor) .srcref(superCall); } private static boolean isSingleSpreadOfArguments(List nodeList) { return nodeList.size() == 1 && isSpreadOfArguments(Iterables.getOnlyElement(nodeList)); } private static boolean isSpreadOfArguments(Node node) { return node.isSpread() && node.getOnlyChild().matchesName("arguments"); } private void replaceNativeErrorSuperCall(Node superCall, Node newSuperCall) { // The native error class constructors always return a new object instead of initializing // `this`, so a workaround is needed. Node superStatement = NodeUtil.getEnclosingStatement(superCall); Node body = superStatement.getParent(); checkState(body.isBlock(), body); AstFactory.Type thisType = type(newSuperCall); // var $jscomp$tmp$error; Node getError = IR.var(astFactory.createName(TMP_ERROR, thisType)).srcrefTreeIfMissing(superCall); getError.insertBefore(superStatement); // Create an expression to initialize `this` from temporary Error object at the point // where super.apply() was called. // $jscomp$tmp$error = Error.call(this, ...), Node getTmpError = astFactory.createAssign(astFactory.createName(TMP_ERROR, thisType), newSuperCall); // this.message = $jscomp$tmp$error.message, Node copyMessage = astFactory.createAssign( astFactory.createGetProp( astFactory.createThis(thisType), "message", type(StandardColors.STRING)), astFactory.createGetProp( astFactory.createName(TMP_ERROR, thisType), "message", type(StandardColors.STRING))); // Old versions of IE Don't set stack until the object is thrown, and won't set it then // if it already exists on the object. // ('stack' in $jscomp$tmp$error) && (this.stack = $jscomp$tmp$error.stack) Node setStack = astFactory.createAnd( astFactory.createIn( astFactory.createString("stack"), astFactory.createName(TMP_ERROR, thisType)), astFactory.createAssign( astFactory.createGetProp( astFactory.createThis(thisType), "stack", type(StandardColors.STRING)), astFactory.createGetProp( astFactory.createName(TMP_ERROR, thisType), "stack", type(StandardColors.STRING)))); Node superErrorExpr = astFactory .createCommas(getTmpError, copyMessage, setStack, astFactory.createThis(thisType)) .srcrefTreeIfMissing(superCall); superCall.replaceWith(superErrorExpr); compiler.reportChangeToEnclosingScope(superErrorExpr); } private boolean isNativeObjectClass(NodeTraversal t, String className) { return className.equals("Object") && !isDefinedInSources(t, className); } private boolean isNativeErrorClass(NodeTraversal t, String superClassName) { switch (superClassName) { // All Error classes listed in the ECMAScript spec as of 2016 case "AggregateError": case "Error": case "EvalError": case "RangeError": case "ReferenceError": case "SyntaxError": case "TypeError": case "URIError": return !isDefinedInSources(t, superClassName); default: return false; } } /** * Is `className` the name of a known native JS class for which we haven't seen a definition in * the source code we're compiling. (Note that our own polyfill definitions don't count as a * definition being present in the source code.) */ private boolean isKnownNativeClass(NodeTraversal t, String className) { // This list originally taken from the list of built-in objects at // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference // as of 2016-10-22. // - Intl.* classes were left out, because it doesn't seem worth the extra effort // of handling the qualified name. // - Deprecated and experimental classes were left out. switch (className) { case "Array": case "ArrayBuffer": case "Boolean": case "DataView": case "Date": case "Float32Array": case "Function": case "Generator": case "GeneratorFunction": case "Int16Array": case "Int32Array": case "Int8Array": case "InternalError": case "Map": case "Number": case "Object": case "Promise": case "Proxy": case "RegExp": case "Set": case "String": case "Symbol": case "TypedArray": case "Uint16Array": case "Uint32Array": case "Uint8Array": case "Uint8ClampedArray": case "WeakMap": case "WeakSet": return !isDefinedInSources(t, className); default: return false; } } /** * Is a variable with the given name defined in the source code being compiled? * *

Please note that the call to {@code t.getScope()} is expensive, so we should avoid calling * this method when possible. */ private boolean isDefinedInSources(NodeTraversal t, String varName) { Var objectVar = t.getScope().getVar(varName); return objectVar != null && !objectVar.isExtern(); } private void updateThisToSuperThis( final AstFactory.Type typeOfThis, Node constructorBody, final List superCalls) { NodeTraversal.Callback replaceThisWithSuperThis = new NodeTraversal.Callback() { @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (superCalls.contains(n)) { return false; // Leave `this` intact on super calls. } else if (n.isFunction() && !n.isArrowFunction()) { // Don't replace `this` in non-arrow function definitions. return false; } else { return true; } } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isThis()) { Node superThis = astFactory.createName(SUPER_THIS, type(n)).srcref(n); n.replaceWith(superThis); } else if (n.isReturn() && !n.hasChildren()) { // An empty return needs to be changed to return $jscomp$super$this n.addChildToFront(astFactory.createName(SUPER_THIS, typeOfThis).srcref(n)); } } }; NodeTraversal.traverse(compiler, constructorBody, replaceThisWithSuperThis); } private AstFactory.Type getTypeOfThisForConstructor(Node constructor) { checkArgument(constructor.isFunction(), constructor); final Color constructorType = constructor.getColor(); return constructorType != null && !constructorType.getInstanceColors().isEmpty() ? type(Color.createUnion(constructorType.getInstanceColors())) : type(StandardColors.UNKNOWN); } private Node getSuperClassQNameNode(Node constructor) { String className = NodeUtil.getNameNode(constructor).getQualifiedName(); Node constructorStatement = checkNotNull(NodeUtil.getEnclosingStatement(constructor)); Node superClassNameNode = null; for (Node statement = constructorStatement.getNext(); statement != null; statement = statement.getNext()) { superClassNameNode = getSuperClassNameNodeIfIsInheritsStatement(statement, className); if (superClassNameNode != null) { break; } } return checkNotNull(superClassNameNode, "$jscomp.inherits() call not found."); } private @Nullable Node getSuperClassNameNodeIfIsInheritsStatement( Node statement, String className) { // $jscomp.inherits(ChildClass, SuperClass); if (!statement.isExprResult()) { return null; } Node callNode = statement.getFirstChild(); if (!callNode.isCall()) { return null; } Node jscompDotInherits = callNode.getFirstChild(); if (!JSCOMP_INHERITS.matches(jscompDotInherits)) { return null; } Node classNameNode = checkNotNull(jscompDotInherits.getNext()); if (classNameNode.matchesQualifiedName(className)) { return checkNotNull(classNameNode.getNext()); } else { return null; } } void setGlobalNamespace(GlobalNamespace globalNamespace) { this.globalNamespace = globalNamespace; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy