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

com.google.javascript.rhino.Node Maven / Gradle / Ivy

/*
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1997-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Norris Boyd
 *   Roger Lawrence
 *   Mike McCabe
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package com.google.javascript.rhino;

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 com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.DoNotCall;
import com.google.javascript.rhino.StaticSourceFile.SourceKind;
import com.google.javascript.rhino.jstype.JSType;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;

/**
 * This class implements the root of the intermediate representation.
 *
 */

public class Node implements Serializable {

  private static final long serialVersionUID = 1L;

  private enum Prop {
    // Contains a JSDocInfo object
    JSDOC_INFO,
    // The name node is a variable length argument placeholder.
    VAR_ARGS,
    // Whether incrdecr is pre (false) or post (true)
    INCRDECR,
    // Set to indicate a quoted object lit key
    QUOTED,
    // The name node is an optional argument.
    OPT_ARG,
    // A synthetic block. Used to make processing simpler, and does not represent a real block in
    // the source.
    SYNTHETIC,
    // Used to indicate BLOCK that is added
    ADDED_BLOCK,
    // The original name of the node, before renaming.
    ORIGINALNAME,
    // Function or constructor call side effect flags.
    SIDE_EFFECT_FLAGS,
    // The variable or property is constant.
    IS_CONSTANT_NAME,
    // The variable creates a namespace.
    IS_NAMESPACE,
    // The ES5 directives on this node.
    DIRECTIVES,
    // ES5 distinguishes between direct and indirect calls to eval.
    DIRECT_EVAL,
    // A CALL without an explicit "this" value.
    FREE_CALL,
    // A StaticSourceFile indicating the file where this node lives.
    SOURCE_FILE,
    // The id of the input associated with this node.
    INPUT_ID,
    // Whether a STRING node contains a \v vertical tab escape. This is a total hack. See comments
    // in IRFactory about this.
    SLASH_V,
    // Marks a function whose parameter types have been inferred.
    INFERRED,
    // For passes that work only on changed funs.
    CHANGE_TIME,
    // An object that's used for goog.object.reflect-style reflection.
    REFLECTED_OBJECT,
    // Set if class member definition is static
    STATIC_MEMBER,
    // Set if the node is a Generator function or member method.
    GENERATOR_FN,
    // Set if the node is an arrow function.
    ARROW_FN,
    // http://tc39.github.io/ecmascript-asyncawait/
    ASYNC_FN,
    // Set if a yield is a "yield all"
    YIELD_ALL,
    // Set if a export is a "default" export
    EXPORT_DEFAULT,
    // Set if an export is a "*"
    EXPORT_ALL_FROM,
    // A lexical variable is inferred const
    IS_CONSTANT_VAR,
    // Used by the ES6-to-ES3 translator.
    IS_GENERATOR_MARKER,
    // Used by the ES6-to-ES3 translator.
    IS_GENERATOR_SAFE,
    // A computed property that has the method syntax
    //   ( [prop]() {...} )
    // rather than the property definition syntax
    //   ( [prop]: value ).
    COMPUTED_PROP_METHOD,
    // A computed property in a getter, e.g. var obj = { get [prop]() {...} };
    COMPUTED_PROP_GETTER,
    // A computed property in a setter, e.g. var obj = 32;
    COMPUTED_PROP_SETTER,
    // A computed property that's a variable, e.g. [prop]: string;
    COMPUTED_PROP_VARIABLE,
    // Used to attach TypeDeclarationNode ASTs to Nodes which represent a typed NAME or FUNCTION.
    DECLARED_TYPE_EXPR,
    // The type of an expression before the cast. This will be present only if the expression is
    // casted.
    TYPE_BEFORE_CAST,
    // The node is an optional parameter or property in ES6 Typed syntax.
    OPT_ES6_TYPED,
    // Generic type list in ES6 typed syntax.
    GENERIC_TYPE,
    // "implements" clause in ES6 typed syntax.
    IMPLEMENTS,
    // This node is a TypeScript ConstructSignature
    CONSTRUCT_SIGNATURE,
    // TypeScript accessibility modifiers (public, protected, private)
    ACCESS_MODIFIER,
    // Indicates the node should not be indexed by analysis tools.
    NON_INDEXABLE,
    // Parse results stored on SCRIPT nodes to allow replaying parse warnings/errors when cloning
    // cached ASTs.
    PARSE_RESULTS,
    // Indicates that a SCRIPT node is a goog.module. Remains set after the goog.module is
    // desugared.
    GOOG_MODULE,
    // Node is a goog.require() as desugared by goog.module()
    GOOG_MODULE_REQUIRE,
    // Attaches a FeatureSet to SCRIPT nodes.
    FEATURE_SET,
    // Indicates that a STRING node represents a namespace from goog.module() or goog.require()
    // call.
    IS_MODULE_NAME,
    // Indicates a namespace that was provided at some point in the past.
    WAS_PREVIOUSLY_PROVIDED,
    // Indicates that a FUNCTION node is converted from an ES6 class
    IS_ES6_CLASS,
    // Indicates that a SCRIPT represents a transpiled file
    TRANSPILED,
    // For passes that work only on deleted funs.
    DELETED,
    // Indicates that the node is an alias or a name from goog.require'd module or ES6
    // module. Aliases are desugared and inlined by compiler passes but we need to preserve them for
    // building index.
    MODULE_ALIAS,
    // Mark a parameter as unused. Used to defer work from RemovedUnusedVars to OptimizeParameters.
    IS_UNUSED_PARAMETER,
    // Mark a property as a module export so that collase properties can act on it.
    MODULE_EXPORT,
    // Indicates that a property {x:x} was originally parsed as {x}.
    IS_SHORTHAND_PROPERTY,
    // Indicates that a SCRIPT node is or was an ES module. Remains set after the module is
    // rewritten.
    ES6_MODULE,
    // Record the type associated with a @typedef to enable looking up typedef in the AST possible
    // without saving the type scope.
    TYPEDEF_TYPE,
    // Original name of a goog.define call.
    DEFINE_NAME,
  }

  // TODO(sdh): Get rid of these by using accessor methods instead.
  // These export instances of a private type, which is awkward but a side effect is that it
  // prevents anyone from introducing problemmatic uses of the general-purpose accessors.
  public static final Prop JSDOC_INFO_PROP = Prop.JSDOC_INFO;
  public static final Prop INCRDECR_PROP = Prop.INCRDECR;
  public static final Prop QUOTED_PROP = Prop.QUOTED;
  public static final Prop ORIGINALNAME_PROP = Prop.ORIGINALNAME;
  public static final Prop IS_CONSTANT_NAME = Prop.IS_CONSTANT_NAME;
  public static final Prop IS_NAMESPACE = Prop.IS_NAMESPACE;
  public static final Prop DIRECT_EVAL = Prop.DIRECT_EVAL;
  public static final Prop FREE_CALL = Prop.FREE_CALL;
  public static final Prop SLASH_V = Prop.SLASH_V;
  public static final Prop REFLECTED_OBJECT = Prop.REFLECTED_OBJECT;
  public static final Prop STATIC_MEMBER = Prop.STATIC_MEMBER;
  public static final Prop GENERATOR_FN = Prop.GENERATOR_FN;
  public static final Prop YIELD_ALL = Prop.YIELD_ALL;
  public static final Prop EXPORT_DEFAULT = Prop.EXPORT_DEFAULT;
  public static final Prop EXPORT_ALL_FROM = Prop.EXPORT_ALL_FROM;
  public static final Prop IS_CONSTANT_VAR = Prop.IS_CONSTANT_VAR;
  public static final Prop COMPUTED_PROP_METHOD = Prop.COMPUTED_PROP_METHOD;
  public static final Prop COMPUTED_PROP_GETTER = Prop.COMPUTED_PROP_GETTER;
  public static final Prop COMPUTED_PROP_SETTER = Prop.COMPUTED_PROP_SETTER;
  public static final Prop COMPUTED_PROP_VARIABLE = Prop.COMPUTED_PROP_VARIABLE;
  public static final Prop OPT_ES6_TYPED = Prop.OPT_ES6_TYPED;
  public static final Prop GENERIC_TYPE_LIST = Prop.GENERIC_TYPE;
  public static final Prop IMPLEMENTS = Prop.IMPLEMENTS;
  public static final Prop CONSTRUCT_SIGNATURE = Prop.CONSTRUCT_SIGNATURE;
  public static final Prop ACCESS_MODIFIER = Prop.ACCESS_MODIFIER;
  public static final Prop PARSE_RESULTS = Prop.PARSE_RESULTS;
  public static final Prop GOOG_MODULE = Prop.GOOG_MODULE;
  public static final Prop FEATURE_SET = Prop.FEATURE_SET;
  public static final Prop IS_MODULE_NAME = Prop.IS_MODULE_NAME;
  public static final Prop WAS_PREVIOUSLY_PROVIDED = Prop.WAS_PREVIOUSLY_PROVIDED;
  public static final Prop IS_ES6_CLASS = Prop.IS_ES6_CLASS;
  public static final Prop TRANSPILED = Prop.TRANSPILED;
  public static final Prop MODULE_ALIAS = Prop.MODULE_ALIAS;
  public static final Prop MODULE_EXPORT = Prop.MODULE_EXPORT;
  public static final Prop IS_SHORTHAND_PROPERTY = Prop.IS_SHORTHAND_PROPERTY;
  public static final Prop ES6_MODULE = Prop.ES6_MODULE;

  private static final String propToString(Prop propType) {
    return Ascii.toLowerCase(String.valueOf(propType));
  }

  /**
   * Represents a node in the type declaration AST.
   */
  public static final class TypeDeclarationNode extends Node {

    private static final long serialVersionUID = 1L;
    private String str; // This is used for specialized signatures.

    public TypeDeclarationNode(Token nodeType, String str) {
      super(nodeType);
      this.str = str;
    }

    public TypeDeclarationNode(Token nodeType) {
      super(nodeType);
    }

    public TypeDeclarationNode(Token nodeType, Node child) {
      super(nodeType, child);
    }

    /**
     * returns the string content.
     * @return non null.
     */
    @Override
    public String getString() {
      return str;
    }

    @Override
    public TypeDeclarationNode cloneNode(boolean cloneTypeExprs) {
      return copyNodeFields(new TypeDeclarationNode(token, str), cloneTypeExprs);
    }
  }

  private static final class NumberNode extends Node {

    private static final long serialVersionUID = 1L;

    NumberNode(double number) {
      super(Token.NUMBER);
      this.number = number;
    }

    public NumberNode(double number, int lineno, int charno) {
      super(Token.NUMBER, lineno, charno);
      this.number = number;
    }

    @Override
    public double getDouble() {
      return this.number;
    }

    @Override
    public void setDouble(double d) {
      this.number = d;
    }

    @Override
    public boolean isEquivalentTo(
        Node node, boolean compareType, boolean recur, boolean jsDoc, boolean sideEffect) {
      boolean equiv = super.isEquivalentTo(node, compareType, recur, jsDoc, sideEffect);
      if (equiv) {
        double thisValue = getDouble();
        double thatValue = ((NumberNode) node).getDouble();
        if (thisValue == thatValue) {
          // detect the difference between 0.0 and -0.0.
          return (thisValue != 0.0) || (1 / thisValue == 1 / thatValue);
        }
      }
      return false;
    }

    private double number;

    @Override
    public NumberNode cloneNode(boolean cloneTypeExprs) {
      return copyNodeFields(new NumberNode(number), cloneTypeExprs);
    }
  }

  private static final class StringNode extends Node {

    private static final long serialVersionUID = 1L;

    // Only for cloneNode
    private StringNode(Token token) {
      super(token);
    }

    StringNode(Token token, String str) {
      super(token);
      setString(str);
    }

    StringNode(Token token, String str, int lineno, int charno) {
      super(token, lineno, charno);
      setString(str);
    }

    /**
     * returns the string content.
     * @return non null.
     */
    @Override
    public String getString() {
      return this.str;
    }

    /**
     * sets the string content.
     * @param str the new value.  Non null.
     */
    @Override
    public void setString(String str) {
      if (null == str) {
        throw new IllegalArgumentException("StringNode: str is null");
      }
      // Intern the string reference so that serialization won't save repeated strings.
      this.str = str.intern();
    }

    @Override
    @SuppressWarnings("ReferenceEquality")
    public boolean isEquivalentTo(
        Node node, boolean compareType, boolean recur, boolean jsDoc, boolean sideEffect) {
      // NOTE: we take advantage of the string interning done in #setString and use
      // '==' rather than 'equals' here to avoid doing unnecessary string equalities.
      return (super.isEquivalentTo(node, compareType, recur, jsDoc, sideEffect)
          && this.str == (((StringNode) node).str));
    }

    /**
     * If the property is not defined, this was not a quoted key.  The
     * Prop.QUOTED int property is only assigned to STRING tokens used as
     * object lit keys.
     * @return true if this was a quoted string key in an object literal.
     */
    @Override
    public boolean isQuotedString() {
      return getBooleanProp(Prop.QUOTED);
    }

    /**
     * This should only be called for STRING nodes created in object lits.
     */
    @Override
    public void setQuotedString() {
      putBooleanProp(Prop.QUOTED, true);
    }

    private String str;

    @Override
    public StringNode cloneNode(boolean cloneTypeExprs) {
      StringNode clone = new StringNode(token);
      clone.str = str;
      return copyNodeFields(clone, cloneTypeExprs);
    }

    @GwtIncompatible("ObjectInputStream")
    private void readObject(java.io.ObjectInputStream in) throws Exception {
      in.defaultReadObject();

      this.str = this.str.intern();
    }
  }

  private static final class TemplateLiteralSubstringNode extends Node {

    private static final long serialVersionUID = 1L;
    // The "cooked" version of the template literal substring. May be null.
    @Nullable
    private String cooked;
    // The raw version of the template literal substring, is not null
    private String raw;

    // Only for cloneNode
    private TemplateLiteralSubstringNode() {
      super(Token.TEMPLATELIT_STRING);
    }

    TemplateLiteralSubstringNode(@Nullable String cooked, String raw) {
      super(Token.TEMPLATELIT_STRING);
      this.cooked = cooked;
      setRaw(raw);
    }

    TemplateLiteralSubstringNode(@Nullable String cooked, String raw,
        int lineno, int charno) {
      super(Token.TEMPLATELIT_STRING, lineno, charno);
      this.cooked = cooked;
      setRaw(raw);
    }

    /**
     * returns the raw string content.
     * @return non null.
     */
    @Override
    public String getRawString() {
      return this.raw;
    }

    /**
     * @return the cooked string content.
     */
    @Override @Nullable
    public String getCookedString() {
      return this.cooked;
    }

    /**
     * sets the raw string content.
     * @param str the new value. Non null.
     */
    public void setRaw(String str) {
      if (null == str) {
        throw new IllegalArgumentException("TemplateLiteralSubstringNode: raw str is null");
      }
      // Intern the string reference so that serialization won't save repeated strings.
      this.raw = str.intern();
    }

    @Override
    @SuppressWarnings("ReferenceEquality")
    public boolean isEquivalentTo(
        Node node, boolean compareType, boolean recur, boolean jsDoc, boolean sideEffect) {
      // NOTE: we take advantage of the string interning done in #setRaw and use
      // '==' rather than 'equals' here to avoid doing unnecessary string equalities.
      return (super.isEquivalentTo(node, compareType, recur, jsDoc, sideEffect)
          && this.raw == ((TemplateLiteralSubstringNode) node).raw
          && Objects.equal(this.cooked, ((TemplateLiteralSubstringNode) node).cooked));
    }

    @Override
    public TemplateLiteralSubstringNode cloneNode(boolean cloneTypeExprs) {
      TemplateLiteralSubstringNode clone = new TemplateLiteralSubstringNode();
      clone.raw = raw;
      clone.cooked = cooked;
      return copyNodeFields(clone, cloneTypeExprs);
    }
  }

  private abstract static class PropListItem implements Serializable {
    final @Nullable PropListItem next;
    final byte propType;

    PropListItem(byte propType, @Nullable PropListItem next) {
      this.propType = propType;
      this.next = next;
    }

    public abstract int getIntValue();
    public abstract Object getObjectValue();
    public abstract PropListItem chain(@Nullable PropListItem next);
  }

  // A base class for Object storing props
  private static final class ObjectPropListItem extends PropListItem {
    private final Object objectValue;

    ObjectPropListItem(byte propType, Object objectValue, @Nullable PropListItem next) {
      super(propType, next);
      this.objectValue = objectValue;
    }

    @Override
    public int getIntValue() {
      throw new UnsupportedOperationException();
    }

    @Override
    public Object getObjectValue() {
      return objectValue;
    }

    @Override
    public String toString() {
      return String.valueOf(objectValue);
    }

    @Override
    public PropListItem chain(@Nullable PropListItem next) {
      return new ObjectPropListItem(propType, objectValue, next);
    }
  }

  // A base class for int storing props
  private static final class IntPropListItem extends PropListItem {
    final int intValue;

    IntPropListItem(byte propType, int intValue, @Nullable PropListItem next) {
      super(propType, next);
      this.intValue = intValue;
    }

    @Override
    public int getIntValue() {
      return intValue;
    }

    @Override
    public Object getObjectValue() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return String.valueOf(intValue);
    }

    @Override
    public PropListItem chain(@Nullable PropListItem next) {
      return new IntPropListItem(propType, intValue, next);
    }
  }

  public Node(Token nodeType) {
    token = nodeType;
    parent = null;
    sourcePosition = -1;
  }

  public Node(Token nodeType, Node child) {
    checkArgument(child.parent == null, "new child has existing parent");
    checkArgument(child.next == null, "new child has existing next sibling");
    checkArgument(child.previous == null, "new child has existing previous sibling");

    token = nodeType;
    parent = null;
    first = child;
    child.next = null;
    child.previous = first;
    child.parent = this;
    sourcePosition = -1;
  }

  public Node(Token nodeType, Node left, Node right) {
    checkArgument(left.parent == null, "first new child has existing parent");
    checkArgument(left.next == null, "first new child has existing next sibling");
    checkArgument(left.previous == null, "first new child has existing previous sibling");
    checkArgument(right.parent == null, "second new child has existing parent");
    checkArgument(right.next == null, "second new child has existing next sibling");
    checkArgument(right.previous == null, "second new child has existing previous sibling");
    token = nodeType;
    parent = null;
    first = left;
    left.next = right;
    left.previous = right;
    left.parent = this;
    right.next = null;
    right.previous = left;
    right.parent = this;
    sourcePosition = -1;
  }

  public Node(Token nodeType, Node left, Node mid, Node right) {
    checkArgument(left.parent == null);
    checkArgument(left.next == null);
    checkArgument(left.previous == null);
    checkArgument(mid.parent == null);
    checkArgument(mid.next == null);
    checkArgument(mid.previous == null);
    checkArgument(right.parent == null);
    checkArgument(right.next == null);
    checkArgument(right.previous == null);
    token = nodeType;
    parent = null;
    first = left;
    left.next = mid;
    left.previous = right;
    left.parent = this;
    mid.next = right;
    mid.previous = left;
    mid.parent = this;
    right.next = null;
    right.previous = mid;
    right.parent = this;
    sourcePosition = -1;
  }

  Node(Token nodeType, Node left, Node mid, Node mid2, Node right) {
    checkArgument(left.parent == null);
    checkArgument(left.next == null);
    checkArgument(left.previous == null);
    checkArgument(mid.parent == null);
    checkArgument(mid.next == null);
    checkArgument(mid.previous == null);
    checkArgument(mid2.parent == null);
    checkArgument(mid2.next == null);
    checkArgument(mid2.previous == null);
    checkArgument(right.parent == null);
    checkArgument(right.next == null);
    checkArgument(right.previous == null);
    token = nodeType;
    parent = null;
    first = left;
    left.next = mid;
    left.previous = right;
    left.parent = this;
    mid.next = mid2;
    mid.previous = left;
    mid.parent = this;
    mid2.next = right;
    mid2.previous = mid;
    mid2.parent = this;
    right.next = null;
    right.previous = mid2;
    right.parent = this;
    sourcePosition = -1;
  }

  public Node(Token nodeType, int lineno, int charno) {
    token = nodeType;
    parent = null;
    sourcePosition = mergeLineCharNo(lineno, charno);
  }

  public Node(Token nodeType, Node child, int lineno, int charno) {
    this(nodeType, child);
    sourcePosition = mergeLineCharNo(lineno, charno);
  }

  public static Node newNumber(double number) {
    return new NumberNode(number);
  }

  public static Node newNumber(double number, int lineno, int charno) {
    return new NumberNode(number, lineno, charno);
  }

  public static Node newString(String str) {
    return new StringNode(Token.STRING, str);
  }

  public static Node newString(Token token, String str) {
    return new StringNode(token, str);
  }

  public static Node newString(String str, int lineno, int charno) {
    return new StringNode(Token.STRING, str, lineno, charno);
  }

  public static Node newString(Token token, String str, int lineno, int charno) {
    return new StringNode(token, str, lineno, charno);
  }

  public static Node newTemplateLitString(String cooked, String raw) {
    return new TemplateLiteralSubstringNode(cooked, raw);
  }

  public final Token getToken() {
    return token;
  }

  public final void setToken(Token token) {
    this.token = token;
  }

  public final boolean hasChildren() {
    return first != null;
  }

  public final Node getOnlyChild() {
    checkState(hasOneChild());
    return first;
  }

  @Nullable
  public final Node getFirstChild() {
    return first;
  }

  /**
   * Get the first child of the first child. This method assumes that the first child exists.
   *
   * @return The first child of the first child.
   */
  @Nullable
  public final Node getFirstFirstChild() {
    return first.first;
  }

  @Nullable
  public final Node getSecondChild() {
    return first.next;
  }

  @Nullable
  public final Node getLastChild() {
    return first != null ? first.previous : null;
  }

  @Nullable
  public final Node getNext() {
    return next;
  }

  @Nullable
  public final Node getPrevious() {
    return this == parent.first ? null : previous;
  }

  @Nullable
  private final Node getPrevious(@Nullable Node firstSibling) {
    return this == firstSibling ? null : previous;
  }

  @Nullable
  public final Node getChildBefore(Node child) {
    return child.getPrevious(first);
  }

  /**
   * Gets the ith child, note that this is O(N) where N is the number of children.
   *
   * @param i The index
   * @return The ith child
   */
  public final Node getChildAtIndex(int i) {
    Node n = first;
    while (i > 0) {
      n = n.next;
      i--;
    }
    return n;
  }

  /**
   * Gets the index of a child, note that this is O(N) where N is the number of children.
   *
   * @param child The child
   * @return The index of the child
   */
  public final int getIndexOfChild(Node child) {
    Node n = first;
    int i = 0;
    while (n != null) {
      if (child == n) {
        return i;
      }

      n = n.next;
      i++;
    }
    return -1;
  }

  public final void addChildToFront(Node child) {
    checkArgument(child.parent == null);
    checkArgument(child.next == null);
    checkArgument(child.previous == null);
    child.parent = this;
    child.next = first;
    if (first == null) {
      // NOTE: child.next remains null
      child.previous = child;
    } else {
      Node last = first.previous;
      // NOTE: last.next remains null
      child.previous = last;
      child.next = first;
      first.previous = child;
    }
    first = child;
  }

  public final void addChildToBack(Node child) {
    checkArgument(
        child.parent == null,
        "Cannot add already-owned child node.\nChild: %s\nExisting parent: %s\nNew parent: %s",
        child, child.parent, this);
    checkArgument(child.next == null);
    checkArgument(child.previous == null);

    if (first == null) {
      // NOTE: child.next remains null
      child.previous = child;
      first = child;
    } else {
      Node last = first.previous;
      last.next = child;
      // NOTE: child.next remains null
      child.previous = last;
      first.previous = child;
    }

    child.parent = this;
  }

  /**
   * Add all children to the front of this node.
   *
   * @param children first of a list of sibling nodes who have no parent.
   *    NOTE: Usually you would get this argument from a removeChildren() call.
   *    A single detached node will not work because its sibling pointers will not be
   *    correctly initialized.
   */
  public final void addChildrenToFront(@Nullable Node children) {
    if (children == null) {
      return; // removeChildren() returns null when there are none
    }
    // NOTE: If there is only one sibling, its previous pointer must point to itself.
    // Null indicates a fully detached node.
    checkNotNull(children.previous, children);
    for (Node child = children; child != null; child = child.next) {
      checkArgument(child.parent == null);
      child.parent = this;
    }

    Node lastSib = children.previous;
    if (first != null) {
      Node last = first.previous;
      // NOTE: last.next remains null
      children.previous = last;
      lastSib.next = first;
      first.previous = lastSib;
    }
    first = children;
  }

  public final void addChildrenToBack(Node children) {
    addChildrenAfter(children, getLastChild());
  }

  /**
   * Add 'child' before 'node'.
   */
  public final void addChildBefore(Node newChild, Node node) {
    checkArgument(node.parent == this, "The existing child node of the parent should not be null.");
    checkArgument(newChild.next == null, "The new child node has next siblings.");
    checkArgument(newChild.previous == null, "The new child node has previous siblings.");
    checkArgument(newChild.parent == null, "The new child node already has a parent.");
    if (first == node) {
      Node last = first.previous;
      // NOTE: last.next remains null
      newChild.parent = this;
      newChild.next = first;
      newChild.previous = last;
      first.previous = newChild;
      first = newChild;
    } else {
      addChildAfter(newChild, node.previous);
    }
  }

  /**
   * Add 'newChild' after 'node'.  If 'node' is null, add it to the front of this node.
   */
  public final void addChildAfter(Node newChild, @Nullable Node node) {
    checkArgument(newChild.next == null, "The new child node has next siblings.");
    checkArgument(newChild.previous == null, "The new child node has previous siblings.");
    // NOTE: newChild.next remains null
    newChild.previous = newChild;
    addChildrenAfter(newChild, node);
  }

  /**
   * Add all children after 'node'. If 'node' is null, add them to the front of this node.
   *
   * @param children first of a list of sibling nodes who have no parent.
   *    NOTE: Usually you would get this argument from a removeChildren() call.
   *    A single detached node will not work because its sibling pointers will not be
   *    correctly initialized.
   */
  public final void addChildrenAfter(@Nullable Node children, @Nullable Node node) {
    if (children == null) {
      return; // removeChildren() returns null when there are none
    }
    checkArgument(node == null || node.parent == this);
    // NOTE: If there is only one sibling, its previous pointer must point to itself.
    // Null indicates a fully detached node.
    checkNotNull(children.previous, children);
    if (node == null) {
      addChildrenToFront(children);
      return;
    }

    for (Node child = children; child != null; child = child.next) {
      checkArgument(child.parent == null);
      child.parent = this;
    }

    Node lastSibling = children.previous;
    Node nodeAfter = node.next;
    lastSibling.next = nodeAfter;
    if (nodeAfter == null) {
      first.previous = lastSibling;
    } else {
      nodeAfter.previous = lastSibling;
    }
    node.next = children;
    children.previous = node;
  }

  /** Detach a child from its parent and siblings. */
  public final void removeChild(Node child) {
    checkState(child.parent == this, "%s is not the parent of %s", this, child);
    checkNotNull(child.previous);

    Node last = first.previous;
    Node prevSibling = child.previous;
    Node nextSibling = child.next;
    if (first == child) {
      first = nextSibling;
      if (nextSibling != null) {
        nextSibling.previous = last;
      }
      // last.next remains null
    } else if (child == last) {
      first.previous = prevSibling;
      prevSibling.next = null;
    } else {
      prevSibling.next = nextSibling;
      nextSibling.previous = prevSibling;
    }

    child.next = null;
    child.previous = null;
    child.parent = null;
  }

  /**
   * Detaches Node and replaces it with newNode.
   */
  public final void replaceWith(Node newNode) {
    parent.replaceChild(this, newNode);
  }

  /** Detaches child from Node and replaces it with newChild. */
  public final void replaceChild(Node child, Node newChild) {
    checkArgument(newChild.next == null, "The new child node has next siblings.");
    checkArgument(newChild.previous == null, "The new child node has previous siblings.");
    checkArgument(newChild.parent == null, "The new child node already has a parent.");
    checkState(child.parent == this, "%s is not the parent of %s", this, child);

    // Copy over important information.
    newChild.useSourceInfoIfMissingFrom(child);
    newChild.parent = this;

    Node nextSibling = child.next;
    Node prevSibling = child.previous;

    Node last = first.previous;

    if (child == prevSibling) {  // first and only child
      first = newChild;
      first.previous = newChild;
    } else {
      if (child == first) {
        first = newChild;
        // prevSibling == last, and last.next remains null
      } else {
        prevSibling.next = newChild;
      }

      if (child == last) {
        first.previous = newChild;
      } else {
        nextSibling.previous = newChild;
      }

      newChild.previous = prevSibling;
    }
    newChild.next = nextSibling;  // maybe null

    child.next = null;
    child.previous = null;
    child.parent = null;
  }

  public final void replaceChildAfter(Node prevChild, Node newChild) {
    checkNotNull(prevChild.next, "prev doesn't have a sibling to replace.");
    replaceChild(prevChild.next, newChild);
  }

  /** Detaches the child after the given child, or the first child if prev is null. */
  public final void replaceFirstOrChildAfter(@Nullable Node prev, Node newChild) {
    Node target = prev == null ? first : prev.next;
    checkNotNull(target, "prev doesn't have a sibling to replace.");
    replaceChild(target, newChild);
  }

  @VisibleForTesting
  @Nullable
  final PropListItem lookupProperty(Prop prop) {
    byte propType = (byte) prop.ordinal();
    PropListItem x = propListHead;
    while (x != null && propType != x.propType) {
      x = x.next;
    }
    return x;
  }

  /**
   * Clone the properties from the provided node without copying
   * the property object.  The receiving node may not have any
   * existing properties.
   * @param other The node to clone properties from.
   * @return this node.
   */
  public final Node clonePropsFrom(Node other) {
    checkState(this.propListHead == null, "Node has existing properties.");
    this.propListHead = other.propListHead;
    return this;
  }

  public final boolean hasProps() {
    return propListHead != null;
  }

  public final void removeProp(Prop propType) {
    PropListItem result = removeProp(propListHead, (byte) propType.ordinal());
    if (result != propListHead) {
      propListHead = result;
    }
  }

  /**
   * @param item The item to inspect
   * @param propType The property to look for
   * @return The replacement list if the property was removed, or 'item' otherwise.
   */
  @Nullable
  private final PropListItem removeProp(@Nullable PropListItem item, byte propType) {
    if (item == null) {
      return null;
    } else if (item.propType == propType) {
      return item.next;
    } else {
      PropListItem result = removeProp(item.next, propType);
      if (result != item.next) {
        return item.chain(result);
      } else {
        return item;
      }
    }
  }

  @Nullable
  public final Object getProp(Prop propType) {
    PropListItem item = lookupProperty(propType);
    if (item == null) {
      return null;
    }
    return item.getObjectValue();
  }

  public final boolean getBooleanProp(Prop propType) {
    return getIntProp(propType) != 0;
  }

  /**
   * Returns the integer value for the property, or 0 if the property
   * is not defined.
   */
  public final int getIntProp(Prop propType) {
    PropListItem item = lookupProperty(propType);
    if (item == null) {
      return 0;
    }
    return item.getIntValue();
  }

  public final int getExistingIntProp(Prop propType) {
    PropListItem item = lookupProperty(propType);
    if (item == null) {
      throw new IllegalStateException("missing prop: " + propType);
    }
    return item.getIntValue();
  }

  public final void putProp(Prop propType, @Nullable Object value) {
    removeProp(propType);
    if (value != null) {
      propListHead = createProp((byte) propType.ordinal(), value, propListHead);
    }
  }

  public final void putBooleanProp(Prop propType, boolean value) {
    putIntProp(propType, value ? 1 : 0);
  }

  public final void putIntProp(Prop propType, int value) {
    removeProp(propType);
    if (value != 0) {
      propListHead = createProp((byte) propType.ordinal(), value, propListHead);
    }
  }

  /**
   * Sets the syntactical type specified on this node.
   * @param typeExpression
   */
  public final void setDeclaredTypeExpression(TypeDeclarationNode typeExpression) {
    putProp(Prop.DECLARED_TYPE_EXPR, typeExpression);
  }

  /**
   * Returns the syntactical type specified on this node. Not to be confused with {@link
   * #getJSType()} which returns the compiler-inferred type.
   */
  @Nullable
  public final TypeDeclarationNode getDeclaredTypeExpression() {
    return (TypeDeclarationNode) getProp(Prop.DECLARED_TYPE_EXPR);
  }

  final PropListItem createProp(byte propType, Object value, @Nullable PropListItem next) {
    return new ObjectPropListItem(propType, value, next);
  }

  final PropListItem createProp(byte propType, int value, @Nullable PropListItem next) {
    return new IntPropListItem(propType, value, next);
  }

  /**
   * Sets the type of this node before casting.
   */
  public final void setJSTypeBeforeCast(JSType type) {
    putProp(Prop.TYPE_BEFORE_CAST, type);
  }

  /**
   * Returns the type of this node before casting. This annotation will only exist on the first
   * child of a CAST node after type checking.
   */
  @Nullable
  public final JSType getJSTypeBeforeCast() {
    return (JSType) getProp(Prop.TYPE_BEFORE_CAST);
  }

  // Gets all the property types, in sorted order.
  private byte[] getSortedPropTypes() {
    int count = 0;
    for (PropListItem x = propListHead; x != null; x = x.next) {
      count++;
    }

    byte[] keys = new byte[count];
    for (PropListItem x = propListHead; x != null; x = x.next) {
      count--;
      keys[count] = x.propType;
    }

    Arrays.sort(keys);
    return keys;
  }

  /** Can only be called when getType() == TokenStream.NUMBER */
  public double getDouble() {
    if (this.token == Token.NUMBER) {
      throw new IllegalStateException(
          "Number node not created with Node.newNumber");
    } else {
      throw new UnsupportedOperationException(this + " is not a number node");
    }
  }

  /**
   * Can only be called when getType() == Token.NUMBER
   *
   * @param value value to set.
   */
  public void setDouble(double value) {
    if (this.token == Token.NUMBER) {
      throw new IllegalStateException(
          "Number node not created with Node.newNumber");
    } else {
      throw new UnsupportedOperationException(this + " is not a string node");
    }
  }

  /** Can only be called when node has String context. */
  public String getString() {
    if (this.token == Token.STRING) {
      throw new IllegalStateException(
          "String node not created with Node.newString");
    } else {
      throw new UnsupportedOperationException(this + " is not a string node");
    }
  }

  /**
   * Can only be called for a Token.STRING or Token.NAME.
   *
   * @param value the value to set.
   */
  public void setString(String value) {
    if (this.token == Token.STRING || this.token == Token.NAME) {
      throw new IllegalStateException(
          "String node not created with Node.newString");
    } else {
      throw new UnsupportedOperationException(this + " is not a string node");
    }
  }

  /** Can only be called when getType() == Token.TEMPLATELIT_STRING */
  public String getRawString() {
    if (this.token == Token.TEMPLATELIT_STRING) {
      throw new IllegalStateException(
          "Template Literal String node not created with Node.newTemplateLitString");
    } else {
      throw new UnsupportedOperationException(this + " is not a template literal string node");
    }
  }

  /** Can only be called when getType() == Token.TEMPLATELIT_STRING */
  @Nullable
  public String getCookedString() {
    if (this.token == Token.TEMPLATELIT_STRING) {
      throw new IllegalStateException(
          "Template Literal String node not created with Node.newTemplateLitString");
    } else {
      throw new UnsupportedOperationException(this + " is not a template literal string node");
    }
  }

  @Override
  public final String toString() {
    return toString(true, true, true);
  }

  public final String toString(
      boolean printSource,
      boolean printAnnotations,
      boolean printType) {
    StringBuilder sb = new StringBuilder();
    toString(sb, printSource, printAnnotations, printType);
    return sb.toString();
  }

  private void toString(
      StringBuilder sb,
      boolean printSource,
      boolean printAnnotations,
      boolean printType) {
    sb.append(token);
    if (this instanceof StringNode) {
      sb.append(' ');
      sb.append(getString());
    } else if (token == Token.FUNCTION) {
      sb.append(' ');
      // In the case of JsDoc trees, the first child is often not a string
      // which causes exceptions to be thrown when calling toString or
      // toStringTree.
      if (first == null || first.token != Token.NAME) {
        sb.append("");
      } else {
        sb.append(first.getString());
      }
    } else if (token == Token.NUMBER) {
      sb.append(' ');
      sb.append(getDouble());
    }
    if (printSource) {
      int lineno = getLineno();
      if (lineno != -1) {
        sb.append(' ');
        sb.append(lineno);
      }
      if (length != 0) {
        sb.append(" [length: ");
        sb.append(length);
        sb.append(']');
      }
    }

    if (printAnnotations) {
      byte[] keys = getSortedPropTypes();
      for (int i = 0; i < keys.length; i++) {
        Prop type = Prop.values()[keys[i]];
        PropListItem x = lookupProperty(type);
        sb.append(" [");
        sb.append(propToString(type));
        sb.append(": ");
        sb.append(x);
        sb.append(']');
      }
    }

    if (printType && jstype != null) {
      String typeString = jstype.toString();
      if (typeString != null) {
        sb.append(" : ");
        sb.append(typeString);
      }
    }
  }

  @CheckReturnValue
  public final String toStringTree() {
    return toStringTreeImpl();
  }

  private String toStringTreeImpl() {
    try {
      StringBuilder s = new StringBuilder();
      appendStringTree(s);
      return s.toString();
    } catch (IOException e) {
      throw new RuntimeException("Should not happen\n" + e);
    }
  }

  public final void appendStringTree(Appendable appendable) throws IOException {
    toStringTreeHelper(this, 0, appendable);
  }

  private static void toStringTreeHelper(Node n, int level, Appendable sb)
      throws IOException {
    for (int i = 0; i != level; ++i) {
      sb.append("    ");
    }
    sb.append(n.toString());
    sb.append('\n');
    for (Node cursor = n.first; cursor != null; cursor = cursor.next) {
      toStringTreeHelper(cursor, level + 1, sb);
    }
  }

  transient Token token;           // Type of the token of the node; NAME for example
  @Nullable transient Node next; // next sibling, a linked list
  @Nullable transient Node previous; // previous sibling, a circular linked list
  @Nullable transient Node first; // first element of a linked list of children
  // We get the last child as first.previous. But last.next is null, not first.

  /**
   * Linked list of properties. Since vast majority of nodes would have no more than 2 properties,
   * linked list saves memory and provides fast lookup. If this does not holds, propListHead can be
   * replaced by UintMap.
   */
  @Nullable private transient PropListItem propListHead;

  /**
   * COLUMN_BITS represents how many of the lower-order bits of
   * sourcePosition are reserved for storing the column number.
   * Bits above these store the line number.
   * This gives us decent position information for everything except
   * files already passed through a minimizer, where lines might
   * be longer than 4096 characters.
   */
  public static final int COLUMN_BITS = 12;

  /**
   * MAX_COLUMN_NUMBER represents the maximum column number that can
   * be represented.  JSCompiler's modifications to Rhino cause all
   * tokens located beyond the maximum column to MAX_COLUMN_NUMBER.
   */
  public static final int MAX_COLUMN_NUMBER = (1 << COLUMN_BITS) - 1;

  /**
   * COLUMN_MASK stores a value where bits storing the column number
   * are set, and bits storing the line are not set.  It's handy for
   * separating column number from line number.
   */
  public static final int COLUMN_MASK = MAX_COLUMN_NUMBER;

  /**
   * Source position of this node. The position is encoded with the
   * column number in the low 12 bits of the integer, and the line
   * number in the rest.  Create some handy constants so we can change this
   * size if we want.
   */
  private transient int sourcePosition;

  /** The length of the code represented by the node. */
  private transient int length;

  @Nullable private transient JSType jstype;

  @Nullable protected transient Node parent;

  //==========================================================================
  // Source position management

  public final void setStaticSourceFileFrom(Node other) {
    // Make sure source file prop nodes are not duplicated.
    if (other.propListHead != null
        && (this.propListHead == null
            || (this.propListHead.propType == Prop.SOURCE_FILE.ordinal()
               && this.propListHead.next == null))) {
      // Either the node has only Prop.SOURCE_FILE as a property or has not properties.
      PropListItem tail = other.propListHead;
      while (tail.next != null) {
        tail = tail.next;
      }
      if (tail.propType == Prop.SOURCE_FILE.ordinal()) {
        propListHead = tail;
        return;
      }
    }
    setStaticSourceFile(other.getStaticSourceFile());
  }

  public final void setStaticSourceFile(@Nullable StaticSourceFile file) {
    this.putProp(Prop.SOURCE_FILE, file);
  }

  /** Sets the source file to a non-extern file of the given name. */
  public final void setSourceFileForTesting(String name) {
    this.putProp(Prop.SOURCE_FILE, new SimpleSourceFile(name, SourceKind.STRONG));
  }

  // TODO(johnlenz): make this final
  @Nullable
  public String getSourceFileName() {
    StaticSourceFile file = getStaticSourceFile();
    return file == null ? null : file.getName();
  }

  /** Returns the source file associated with this input. */
  @Nullable
  public StaticSourceFile getStaticSourceFile() {
    return ((StaticSourceFile) this.getProp(Prop.SOURCE_FILE));
  }

  /**
   * @param inputId
   */
  public void setInputId(InputId inputId) {
    this.putProp(Prop.INPUT_ID, inputId);
  }

  /** @return The Id of the CompilerInput associated with this Node. */
  @Nullable
  public InputId getInputId() {
    return ((InputId) this.getProp(Prop.INPUT_ID));
  }

  /** The original name of this node, if the node has been renamed. */
  @Nullable
  public String getOriginalName() {
    return (String) this.getProp(Prop.ORIGINALNAME);
  }

  public void setOriginalName(String originalName) {
    this.putProp(Prop.ORIGINALNAME, originalName);
  }

  /**
   * Whether this node should be indexed by static analysis / code indexing tools.
   */
  public final boolean isIndexable() {
    return !this.getBooleanProp(Prop.NON_INDEXABLE);
  }

  public final void makeNonIndexable() {
    this.putBooleanProp(Prop.NON_INDEXABLE, true);
  }

  public final void makeNonIndexableRecursive() {
    this.makeNonIndexable();
    for (Node child : children()) {
      child.makeNonIndexableRecursive();
    }
  }

  public final boolean isFromExterns() {
    StaticSourceFile file = getStaticSourceFile();
    return file == null ? false : file.isExtern();
  }

  public final int getLength() {
    return this.length;
  }

  public final void setLength(int length) {
    this.length = length;
  }

  /** Useful to set length of a transpiled node tree to map back to the length of original node. */
  public final void setLengthForTree(int length) {
    this.length = length;

    for (Node child = first; child != null; child = child.next) {
      child.setLengthForTree(length);
    }
  }

  public final int getLineno() {
    return extractLineno(sourcePosition);
  }

  // Returns the 0-based column number
  public final int getCharno() {
    return extractCharno(sourcePosition);
  }

  // TODO(johnlenz): make this final
  public int getSourceOffset() {
    StaticSourceFile file = getStaticSourceFile();
    if (file == null) {
      return -1;
    }
    int lineno = getLineno();
    if (lineno == -1) {
      return -1;
    }
    return file.getLineOffset(lineno) + getCharno();
  }

  public final int getSourcePosition() {
    return sourcePosition;
  }

  public final void setLineno(int lineno) {
      int charno = getCharno();
      if (charno == -1) {
        charno = 0;
      }
      sourcePosition = mergeLineCharNo(lineno, charno);
  }

  public final void setCharno(int charno) {
      sourcePosition = mergeLineCharNo(getLineno(), charno);
  }

  public final void setSourceEncodedPosition(int sourcePosition) {
    this.sourcePosition = sourcePosition;
  }

  public final void setSourceEncodedPositionForTree(int sourcePosition) {
    this.sourcePosition = sourcePosition;

    for (Node child = first; child != null; child = child.next) {
      child.setSourceEncodedPositionForTree(sourcePosition);
    }
  }

  /**
   * Merges the line number and character number in one integer. The Character
   * number takes the first 12 bits and the line number takes the rest. If
   * the character number is greater than 212-1 it is
   * adjusted to 212-1.
   */
  protected static int mergeLineCharNo(int lineno, int charno) {
    if (lineno < 0 || charno < 0) {
      return -1;
    } else if ((charno & ~COLUMN_MASK) != 0) {
      return lineno << COLUMN_BITS | COLUMN_MASK;
    } else {
      return lineno << COLUMN_BITS | (charno & COLUMN_MASK);
    }
  }

  /**
   * Extracts the line number and character number from a merged line char
   * number (see {@link #mergeLineCharNo(int, int)}).
   */
  protected static int extractLineno(int lineCharNo) {
    if (lineCharNo == -1) {
      return -1;
    } else {
      return lineCharNo >>> COLUMN_BITS;
    }
  }

  /**
   * Extracts the character number and character number from a merged line
   * char number (see {@link #mergeLineCharNo(int, int)}).
   */
  protected static int extractCharno(int lineCharNo) {
    if (lineCharNo == -1) {
      return -1;
    } else {
      return lineCharNo & COLUMN_MASK;
    }
  }

  //==========================================================================
  // Iteration

  /**
   * 

Return an iterable object that iterates over this node's children. * The iterator does not support the optional operation * {@link Iterator#remove()}.

* *

To iterate over a node's children, one can write

*
Node n = ...;
   * for (Node child : n.children()) { ...
*/ public final Iterable children() { if (first == null) { return Collections.emptySet(); } else { return new SiblingNodeIterable(first); } } /** *

Return an iterable object that iterates over this node's siblings, * including this Node but not any siblings that are before this one. * *

The iterator does not support the optional * operation {@link Iterator#remove()}.

* *

To iterate over a node's siblings including itself, one can write

*
Node n = ...;
   * for (Node sibling : n.siblings()) { ...
*/ public final Iterable siblings() { return new SiblingNodeIterable(this); } /** * @see Node#siblings() */ private static final class SiblingNodeIterable implements Iterable { private final Node start; SiblingNodeIterable(Node start) { this.start = start; } @Override public Iterator iterator() { return new SiblingNodeIterator(start); } } /** * @see Node#siblings() */ private static final class SiblingNodeIterator implements Iterator { @Nullable private Node current; SiblingNodeIterator(Node start) { this.current = start; } @Override public boolean hasNext() { return current != null; } @Override public Node next() { if (current == null) { throw new NoSuchElementException(); } Node n = current; current = current.getNext(); return n; } @Override public void remove() { throw new UnsupportedOperationException(); } } // ========================================================================== // Accessors @Nullable final PropListItem getPropListHeadForTesting() { return propListHead; } final void setPropListHead(@Nullable PropListItem propListHead) { this.propListHead = propListHead; } @Nullable public final Node getParent() { return parent; } @Nullable public final Node getGrandparent() { return parent == null ? null : parent.parent; } /** * Gets the ancestor node relative to this. * * @param level 0 = this, 1 = the parent, etc. */ @Nullable public final Node getAncestor(int level) { checkArgument(level >= 0); Node node = this; while (node != null && level-- > 0) { node = node.getParent(); } return node; } /** @return True if this Node is {@code node} or a descendant of {@code node}. */ public final boolean isDescendantOf(Node node) { for (Node n = this; n != null; n = n.parent) { if (n == node) { return true; } } return false; } public final boolean isOnlyChildOf(Node possibleParent) { return possibleParent == getParent() && getPrevious() == null && getNext() == null; } public final boolean isFirstChildOf(Node possibleParent) { return possibleParent == getParent() && getPrevious() == null; } public final boolean isSecondChildOf(Node possibleParent) { Node previousNode = getPrevious(); return previousNode != null && previousNode.isFirstChildOf(possibleParent); } /** * Iterates all of the node's ancestors excluding itself. */ public final AncestorIterable getAncestors() { return new AncestorIterable(checkNotNull(this.getParent())); } /** * Iterator to go up the ancestor tree. */ public static final class AncestorIterable implements Iterable { @Nullable private Node cur; /** * @param cur The node to start. */ AncestorIterable(Node cur) { this.cur = cur; } @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return cur != null; } @Override public Node next() { if (!hasNext()) { throw new NoSuchElementException(); } Node n = cur; cur = cur.getParent(); return n; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } } /** * Check for one child more efficiently than by iterating over all the * children as is done with Node.getChildCount(). * * @return Whether the node has exactly one child. */ public final boolean hasOneChild() { return first != null && first.next == null; } /** * Check for two children more efficiently than {@code getChildCount() == 2} * * @return Whether the node has exactly two children. */ public final boolean hasTwoChildren() { return first != null && first.next != null && first.next == getLastChild(); } /** * Check for zero or one child more efficiently than by iterating over all the * children as is done with Node.getChildCount(). * * @return Whether the node has no children or exactly one child. */ public final boolean hasZeroOrOneChild() { return first == getLastChild(); } /** * Check for more than one child more efficiently than by iterating over all * the children as is done with Node.getChildCount(). * * @return Whether the node more than one child. */ public final boolean hasMoreThanOneChild() { return first != null && first.next != null; } /** * Check for has exactly the number of specified children. * * @return Whether the node has exactly the number of children specified. */ public final boolean hasXChildren(int x) { int c = 0; for (Node n = first; n != null && c <= x; n = n.next) { c++; } return c == x; } public final int getChildCount() { int c = 0; for (Node n = first; n != null; n = n.next) { c++; } return c; } // Intended for testing and verification only. public final boolean hasChild(Node child) { for (Node n = first; n != null; n = n.next) { if (child == n) { return true; } } return false; } /** Checks equivalence without going into child nodes */ public final boolean isEquivalentToShallow(Node node) { return isEquivalentTo(node, false, false, false, false); } /** Returns true if this node is equivalent semantically to another including side effects. */ public final boolean isEquivalentWithSideEffectsTo(Node node) { return isEquivalentTo(node, false, true, false, true); } /** Returns true if this node is equivalent semantically to another including side effects. */ public final boolean isEquivalentWithSideEffectsToShallow(Node node) { return isEquivalentTo(node, false, false, false, true); } /** * Returns true if this node is equivalent semantically to another and the types are equivalent. */ public final boolean isEquivalentToTyped(Node node) { return isEquivalentTo(node, true, true, true, false); } /** Returns true if this node is equivalent semantically to another */ public final boolean isEquivalentTo(Node node) { return isEquivalentTo(node, false, true, false, false); } /** * @param compareType Whether to compare the JSTypes of the nodes. * @param recurse Whether to compare the children of the current node. If not, only the count of * the children are compared. * @param jsDoc Whether to check that the JsDoc of the nodes are equivalent. * @return Whether this node is equivalent semantically to the provided node. */ final boolean isEquivalentTo(Node node, boolean compareType, boolean recurse, boolean jsDoc) { return isEquivalentTo(node, compareType, recurse, jsDoc, false); } /** * @param compareType Whether to compare the JSTypes of the nodes. * @param recurse Whether to compare the children of the current node. If not, only the count * of the children are compared. * @param jsDoc Whether to check that the JsDoc of the nodes are equivalent. * @param sideEffect Whether to check that the side-effect flags of the nodes are equivalent. * @return Whether this node is equivalent semantically to the provided node. */ public boolean isEquivalentTo( Node node, boolean compareType, boolean recurse, boolean jsDoc, boolean sideEffect) { if (token != node.token || getChildCount() != node.getChildCount() || this.getClass() != node.getClass()) { return false; } if (compareType && !JSType.isEquivalent(getJSType(), node.getJSType())) { return false; } if (jsDoc && !JSDocInfo.areEquivalent(getJSDocInfo(), node.getJSDocInfo())) { return false; } TypeDeclarationNode thisTDN = this.getDeclaredTypeExpression(); TypeDeclarationNode thatTDN = node.getDeclaredTypeExpression(); if ((thisTDN != null || thatTDN != null) && (thisTDN == null || thatTDN == null || !thisTDN.isEquivalentTo(thatTDN, compareType, recurse, jsDoc))) { return false; } for (Function getter : PROP_GETTERS_FOR_EQUALITY) { if (!Objects.equal(getter.apply(this), getter.apply(node))) { return false; } } if (sideEffect) { if (this.getSideEffectFlags() != node.getSideEffectFlags()) { return false; } if (this.isUnusedParameter() != node.isUnusedParameter()) { return false; } } if (recurse) { for (Node n = first, n2 = node.first; n != null; n = n.next, n2 = n2.next) { if (!n.isEquivalentTo(n2, compareType, recurse, jsDoc, sideEffect)) { return false; } } } return true; } /** * Accessors for {@link Node} properties that should also be compared when comparing nodes for * equality. * *

We'd prefer to list the props that should be ignored rather than the ones that should be * checked, but that was too difficult initially. In general these properties are ones that show * up as keywords / symbols which don't have their own nodes. * *

Accessor functions are used rather than {@link Prop}s to encode the correct way of reading * the prop. */ private static final ImmutableList> PROP_GETTERS_FOR_EQUALITY = ImmutableList.of( Node::isArrowFunction, Node::isAsyncFunction, Node::isAsyncGeneratorFunction, Node::isGeneratorFunction, Node::isStaticMember, Node::isYieldAll, (n) -> n.getIntProp(Prop.SLASH_V), (n) -> n.getIntProp(Prop.INCRDECR), (n) -> n.getIntProp(Prop.QUOTED), (n) -> n.getBooleanProp(Prop.FREE_CALL), (n) -> n.getBooleanProp(Prop.COMPUTED_PROP_METHOD), (n) -> n.getBooleanProp(Prop.COMPUTED_PROP_GETTER), (n) -> n.getBooleanProp(Prop.COMPUTED_PROP_SETTER)); /** * This function takes a set of GETPROP nodes and produces a string that is each property * separated by dots. If the node ultimately under the left sub-tree is not a simple name, this is * not a valid qualified name. * * @return a null if this is not a qualified name, or a dot-separated string of the name and * properties. */ @Nullable public final String getQualifiedName() { switch (token) { case NAME: String name = getString(); return name.isEmpty() ? null : name; case GETPROP: StringBuilder builder = getQualifiedNameForGetProp(0); return builder != null ? builder.toString() : null; case THIS: return "this"; case SUPER: return "super"; default: return null; } } @Nullable public final QualifiedName getQualifiedNameObject() { return isQualifiedName() ? new QualifiedName.NodeQname(this) : null; } /** * Helper method for {@link #getQualifiedName} to handle GETPROP nodes. * * @param reserve The number of characters of space to reserve in the StringBuilder * @return {@code null} if this is not a qualified name or a StringBuilder if it is a complex * qualified name. */ @Nullable private StringBuilder getQualifiedNameForGetProp(int reserve) { String propName = getLastChild().getString(); reserve += 1 + propName.length(); // +1 for the '.' StringBuilder builder; if (first.isGetProp()) { builder = first.getQualifiedNameForGetProp(reserve); if (builder == null) { return null; } } else { String left = first.getQualifiedName(); if (left == null) { return null; } builder = new StringBuilder(left.length() + reserve); builder.append(left); } builder.append('.').append(propName); return builder; } /** * This function takes a set of GETPROP nodes and produces a string that is each property * separated by dots. If the node ultimately under the left sub-tree is not a simple name, this is * not a valid qualified name. This method returns the original name of each segment rather than * the renamed version. * * @return a null if this is not a qualified name, or a dot-separated string of the name and * properties. */ @Nullable public final String getOriginalQualifiedName() { if (token == Token.NAME || getBooleanProp(Prop.IS_MODULE_NAME)) { String name = getOriginalName(); if (name == null) { name = getString(); } return name.isEmpty() ? null : name; } else if (token == Token.GETPROP) { String left = getFirstChild().getOriginalQualifiedName(); if (left == null) { return null; } String right = getLastChild().getOriginalName(); if (right == null) { right = getLastChild().getString(); } return left + "." + right; } else if (token == Token.THIS) { return "this"; } else if (token == Token.SUPER) { return "super"; } else { return null; } } /** * Returns whether a node corresponds to a simple or a qualified name, such as * x or a.b.c or this.a. */ public final boolean isQualifiedName() { switch (this.getToken()) { case NAME: return !getString().isEmpty(); case THIS: case SUPER: return true; case GETPROP: return getFirstChild().isQualifiedName(); case MEMBER_FUNCTION_DEF: // These are explicitly *not* qualified name components. default: return false; } } /** * Returns whether a node matches a simple or a qualified name, such as * x or a.b.c or this.a. */ public final boolean matchesQualifiedName(String name) { return matchesQualifiedName(name, name.length()); } /** * Returns whether a node matches a simple or a qualified name, such as * x or a.b.c or this.a. */ private boolean matchesQualifiedName(String qname, int endIndex) { int start = qname.lastIndexOf('.', endIndex - 1) + 1; switch (this.getToken()) { case NAME: String name = getString(); return start == 0 && !name.isEmpty() && name.length() == endIndex && qname.startsWith(name); case THIS: return start == 0 && 4 == endIndex && qname.startsWith("this"); case SUPER: return start == 0 && 5 == endIndex && qname.startsWith("super"); case GETPROP: String prop = getLastChild().getString(); return start > 1 && prop.length() == endIndex - start && prop.regionMatches(0, qname, start, endIndex - start) && getFirstChild().matchesQualifiedName(qname, start - 1); case MEMBER_FUNCTION_DEF: // These are explicitly *not* qualified name components. default: return false; } } /** * Returns whether a node matches a simple or a qualified name, such as x or * a.b.c or this.a. */ @SuppressWarnings("ReferenceEquality") public final boolean matchesQualifiedName(Node n) { if (n.token != token) { return false; } switch (token) { case NAME: // ==, rather than equal as it is intern'd in setString return !getString().isEmpty() && getString() == n.getString(); case THIS: case SUPER: return true; case GETPROP: // ==, rather than equal as it is intern'd in setString return getLastChild().getString() == n.getLastChild().getString() && getFirstChild().matchesQualifiedName(n.getFirstChild()); case MEMBER_FUNCTION_DEF: // These are explicitly *not* qualified name components. default: return false; } } /** * Returns whether a node corresponds to a simple or a qualified name without * a "this" reference, such as a.b.c, but not this.a * . */ public final boolean isUnscopedQualifiedName() { switch (this.getToken()) { case NAME: return !getString().isEmpty(); case GETPROP: return getFirstChild().isUnscopedQualifiedName(); default: return false; } } public final boolean isValidAssignmentTarget() { switch (this.getToken()) { case NAME: case GETPROP: case GETELEM: case ARRAY_PATTERN: case OBJECT_PATTERN: return true; default: return false; } } // ========================================================================== // Mutators /** * Removes this node from its parent. Equivalent to: * node.getParent().removeChild(); */ public final Node detachFromParent() { return detach(); } /** * Removes this node from its parent. Equivalent to: * node.getParent().removeChild(); */ public final Node detach() { checkNotNull(parent); parent.removeChild(this); return this; } /** * Removes the first child of Node. Equivalent to: node.removeChild(node.getFirstChild()); * * @return The removed Node. */ @Nullable public final Node removeFirstChild() { Node child = first; if (child != null) { removeChild(child); } return child; } /** @return A Node that is the head of the list of children. */ @Nullable public final Node removeChildren() { Node children = first; for (Node child = first; child != null; child = child.next) { child.parent = null; } first = null; return children; } /** * Removes all children from this node and isolates the children from each * other. */ public final void detachChildren() { for (Node child = first; child != null;) { Node nextChild = child.next; child.parent = null; child.next = null; child.previous = null; child = nextChild; } first = null; } public final Node removeChildAfter(Node prev) { Node target = prev.next; checkNotNull(target, "no next sibling."); removeChild(target); return target; } /** Remove the child after the given child, or the first child if given null. */ public final Node removeFirstOrChildAfter(@Nullable Node prev) { checkArgument(prev == null || prev.parent == this, "invalid node."); Node target = prev == null ? first : prev.next; checkNotNull(target, "no next sibling."); removeChild(target); return target; } /** * @return A detached clone of the Node, specifically excluding its children. */ @CheckReturnValue public final Node cloneNode() { return cloneNode(false); } /** * @return A detached clone of the Node, specifically excluding its children. */ @CheckReturnValue protected Node cloneNode(boolean cloneTypeExprs) { return copyNodeFields(new Node(token), cloneTypeExprs); } final T copyNodeFields(T dst, boolean cloneTypeExprs) { dst.setSourceEncodedPosition(this.sourcePosition); dst.setLength(this.getLength()); dst.setJSType(this.jstype); dst.setPropListHead(this.propListHead); // TODO(johnlenz): Remove this once JSTypeExpression are immutable if (cloneTypeExprs) { JSDocInfo info = this.getJSDocInfo(); if (info != null) { this.setJSDocInfo(info.clone(true)); } } return dst; } /** * @return A detached clone of the Node and all its children. */ @CheckReturnValue public final Node cloneTree() { return cloneTree(false); } @CheckReturnValue public final Node cloneTree(boolean cloneTypeExprs) { Node result = cloneNode(cloneTypeExprs); Node firstChild = null; Node lastChild = null; if (this.hasChildren()) { for (Node n2 = getFirstChild(); n2 != null; n2 = n2.next) { Node n2clone = n2.cloneTree(cloneTypeExprs); n2clone.parent = result; if (firstChild == null) { firstChild = n2clone; lastChild = firstChild; } else { lastChild.next = n2clone; n2clone.previous = lastChild; lastChild = n2clone; } } firstChild.previous = lastChild; lastChild.next = null; result.first = firstChild; } return result; } /** * Overwrite all the source information in this node with * that of {@code other}. */ public final Node useSourceInfoFrom(Node other) { setStaticSourceFileFrom(other); putProp(Prop.ORIGINALNAME, other.getProp(Prop.ORIGINALNAME)); sourcePosition = other.sourcePosition; length = other.length; return this; } public final Node srcref(Node other) { return useSourceInfoFrom(other); } /** * Overwrite all the source information in this node and its subtree with * that of {@code other}. */ public final Node useSourceInfoFromForTree(Node other) { useSourceInfoFrom(other); for (Node child = first; child != null; child = child.next) { child.useSourceInfoFromForTree(other); } return this; } public final Node srcrefTree(Node other) { return useSourceInfoFromForTree(other); } /** * Overwrite all the source information in this node with * that of {@code other} iff the source info is missing. */ public final Node useSourceInfoIfMissingFrom(Node other) { if (getStaticSourceFile() == null) { setStaticSourceFileFrom(other); sourcePosition = other.sourcePosition; length = other.length; } // TODO(lharker): should this be inside the above if condition? // If the node already has a source file, it seems strange to // go ahead and set the original name anyway. if (getProp(Prop.ORIGINALNAME) == null) { putProp(Prop.ORIGINALNAME, other.getProp(Prop.ORIGINALNAME)); } return this; } /** * Overwrite all the source information in this node and its subtree with * that of {@code other} iff the source info is missing. */ public final Node useSourceInfoIfMissingFromForTree(Node other) { useSourceInfoIfMissingFrom(other); for (Node child = first; child != null; child = child.next) { child.useSourceInfoIfMissingFromForTree(other); } return this; } //========================================================================== // Custom annotations /** * Returns the compiled inferred type on this node. Not to be confused with {@link * #getDeclaredTypeExpression()} which returns the syntactically specified type. */ @Nullable public final JSType getJSType() { return jstype; } /** Returns the compiled inferred type on this node, or throws an NPE if there isn't one. */ public final JSType getJSTypeRequired() { checkNotNull(jstype, "no jstype: %s", this); return jstype; } public final Node setJSType(@Nullable JSType jstype) { this.jstype = jstype; return this; } /** * Get the {@link JSDocInfo} attached to this node. * * @return the information or {@code null} if no JSDoc is attached to this node */ @Nullable public final JSDocInfo getJSDocInfo() { return (JSDocInfo) getProp(Prop.JSDOC_INFO); } /** * Sets the {@link JSDocInfo} attached to this node. */ public final Node setJSDocInfo(JSDocInfo info) { putProp(Prop.JSDOC_INFO, info); return this; } /** This node was last changed at {@code time} */ public final void setChangeTime(int time) { putIntProp(Prop.CHANGE_TIME, time); } /** Returns the time of the last change for this node */ public final int getChangeTime() { return getIntProp(Prop.CHANGE_TIME); } public final void setDeleted(boolean deleted) { putBooleanProp(Prop.DELETED, deleted); } public final boolean isDeleted() { return getBooleanProp(Prop.DELETED); } /** If this node represents a typedef declaration, the associated JSType */ public final void setTypedefTypeProp(JSType type) { putProp(Prop.TYPEDEF_TYPE, type); } /** If this node represents a typedef declaration, the associated JSType */ public final JSType getTypedefTypeProp() { return (JSType) getProp(Prop.TYPEDEF_TYPE); } /** @param unused Whether a parameter was function to be unused. Set by RemoveUnusedVars */ public final void setUnusedParameter(boolean unused) { putBooleanProp(Prop.IS_UNUSED_PARAMETER, unused); } /** * @return Whether a parameter was function to be unused. Set by RemoveUnusedVars */ public final boolean isUnusedParameter() { return getBooleanProp(Prop.IS_UNUSED_PARAMETER); } /** Sets the isShorthandProperty annotation. */ public final void setShorthandProperty(boolean shorthand) { putBooleanProp(Prop.IS_SHORTHAND_PROPERTY, shorthand); } /** Whether this {x:x} property was originally parsed as {x}. */ public final boolean isShorthandProperty() { return getBooleanProp(Prop.IS_SHORTHAND_PROPERTY); } /** * Sets whether this node is a variable length argument node. This * method is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public final void setVarArgs(boolean varArgs) { putBooleanProp(Prop.VAR_ARGS, varArgs); } /** * Returns whether this node is a variable length argument node. This * method's return value is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public final boolean isVarArgs() { return getBooleanProp(Prop.VAR_ARGS); } /** * Sets whether this node is an optional argument node. This * method is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public final void setOptionalArg(boolean optionalArg) { putBooleanProp(Prop.OPT_ARG, optionalArg); } /** * Returns whether this node is an optional argument node. This * method's return value is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public final boolean isOptionalArg() { return getBooleanProp(Prop.OPT_ARG); } /** * Returns whether this node is an optional node in the ES6 Typed syntax. */ public final boolean isOptionalEs6Typed() { return getBooleanProp(Prop.OPT_ES6_TYPED); } /** * Sets whether this is a synthetic block that should not be considered * a real source block. */ public final void setIsSyntheticBlock(boolean val) { checkState(token == Token.BLOCK); putBooleanProp(Prop.SYNTHETIC, val); } /** * Returns whether this is a synthetic block that should not be considered * a real source block. */ public final boolean isSyntheticBlock() { return getBooleanProp(Prop.SYNTHETIC); } /** * Sets the ES5 directives on this node. */ public final void setDirectives(Set val) { putProp(Prop.DIRECTIVES, val); } /** Returns the set of ES5 directives for this node. */ @SuppressWarnings("unchecked") @Nullable public final Set getDirectives() { return (Set) getProp(Prop.DIRECTIVES); } /** * Sets whether this is an added block that should not be considered * a real source block. Eg: In "if (true) x;", the "x;" is put under an added * block in the AST. */ public final void setIsAddedBlock(boolean val) { putBooleanProp(Prop.ADDED_BLOCK, val); } /** * Returns whether this is an added block that should not be considered * a real source block. */ public final boolean isAddedBlock() { return getBooleanProp(Prop.ADDED_BLOCK); } /** * Sets whether this node is a static member node. This * method is meaningful only on {@link Token#GETTER_DEF}, * {@link Token#SETTER_DEF} or {@link Token#MEMBER_FUNCTION_DEF} nodes contained * within {@link Token#CLASS}. */ public final void setStaticMember(boolean isStatic) { putBooleanProp(Prop.STATIC_MEMBER, isStatic); } /** * Returns whether this node is a static member node. This * method is meaningful only on {@link Token#GETTER_DEF}, * {@link Token#SETTER_DEF} or {@link Token#MEMBER_FUNCTION_DEF} nodes contained * within {@link Token#CLASS}. */ public final boolean isStaticMember() { return getBooleanProp(Prop.STATIC_MEMBER); } /** * Sets whether this node is a generator node. This * method is meaningful only on {@link Token#FUNCTION} or * {@link Token#MEMBER_FUNCTION_DEF} nodes. */ public final void setIsGeneratorFunction(boolean isGenerator) { putBooleanProp(Prop.GENERATOR_FN, isGenerator); } /** * Returns whether this node is a generator function node. */ public final boolean isGeneratorFunction() { return getBooleanProp(Prop.GENERATOR_FN); } /** * Sets whether this node subtree contains YIELD nodes. * *

It's used in the translation of generators. */ public final void setGeneratorMarker(boolean isGeneratorMarker) { putBooleanProp(Prop.IS_GENERATOR_MARKER, isGeneratorMarker); } /** * Returns whether this node was marked as containing YIELD nodes. * *

It's used in the translation of generators. */ public final boolean isGeneratorMarker() { return getBooleanProp(Prop.IS_GENERATOR_MARKER); } /** * @see #isGeneratorSafe() */ public final void setGeneratorSafe(boolean isGeneratorSafe) { putBooleanProp(Prop.IS_GENERATOR_SAFE, isGeneratorSafe); } /** * Used when translating ES6 generators. If this returns true, this Node * was generated by the compiler, and it is safe to copy this node to the * transpiled output with no further changes. */ public final boolean isGeneratorSafe() { return getBooleanProp(Prop.IS_GENERATOR_SAFE); } /** * Sets whether this node is a arrow function node. This * method is meaningful only on {@link Token#FUNCTION} */ public final void setIsArrowFunction(boolean isArrow) { checkState(isFunction()); putBooleanProp(Prop.ARROW_FN, isArrow); } /** * Returns whether this node is a arrow function node. */ public final boolean isArrowFunction() { return isFunction() && getBooleanProp(Prop.ARROW_FN); } /** * Sets whether this node is an async function node. This * method is meaningful only on {@link Token#FUNCTION} */ public void setIsAsyncFunction(boolean isAsync) { checkState(isFunction()); putBooleanProp(Prop.ASYNC_FN, isAsync); } /** * Returns whether this is an async function node. */ public final boolean isAsyncFunction() { return isFunction() && getBooleanProp(Prop.ASYNC_FN); } /** Returns whether this is an async generator function node. */ public final boolean isAsyncGeneratorFunction() { return isAsyncFunction() && isGeneratorFunction(); } /** * Sets whether this node is a generator node. This * method is meaningful only on {@link Token#FUNCTION} or * {@link Token#MEMBER_FUNCTION_DEF} nodes. */ public final void setYieldAll(boolean isGenerator) { putBooleanProp(Prop.YIELD_ALL, isGenerator); } /** * Returns whether this node is a generator node. This * method is meaningful only on {@link Token#FUNCTION} or * {@link Token#MEMBER_FUNCTION_DEF} nodes. */ public final boolean isYieldAll() { return getBooleanProp(Prop.YIELD_ALL); } /** Returns true if this is or ever was a CLASS node (i.e. even after transpilation). */ public final boolean isEs6Class() { return isClass() || getBooleanProp(Prop.IS_ES6_CLASS); } /** Returns any goog.define'd name corresponding to this NAME or GETPROP node. */ public final String getDefineName() { return (String) getProp(Prop.DEFINE_NAME); } /** Sets the goog.define name for a NAME or GETPROP node. */ public final void setDefineName(String name) { putProp(Prop.DEFINE_NAME, name); } /** * Marks this function or constructor call's side effect flags. * This property is only meaningful for {@link Token#CALL} and * {@link Token#NEW} nodes. */ public final void setSideEffectFlags(int flags) { checkState( this.isCall() || this.isNew() || this.isTaggedTemplateLit(), "Side-effect flags can only be set on invocation nodes; got %s", this); // We invert the flags before setting because we also invert the them when getting; they go // full circle. // // We apply the mask after inversion so that if all flags are being set (all 1s), it's // equivalent to storing a 0, the default value of an int-prop, which has no memory cost. putIntProp(Prop.SIDE_EFFECT_FLAGS, ~flags & SideEffectFlags.USED_BITS_MASK); } public final void setSideEffectFlags(SideEffectFlags flags) { setSideEffectFlags(flags.valueOf()); } /** * Returns the side effects flags for this node. */ public final int getSideEffectFlags() { // Int props default to 0, but we want the default for side-effect flags to be all 1s. // Therefore, we invert the value returned here. This is correct for non-defaults because we // also invert when setting the flags. return ~getIntProp(Prop.SIDE_EFFECT_FLAGS) & SideEffectFlags.USED_BITS_MASK; } /** * A helper class for getting and setting invocation side-effect flags. * *

The following values are of interest: * *

    *
  1. Is global state mutated? ({@code MUTATES_GLOBAL_STATE}) *
  2. Is the receiver (`this`) mutated? ({@code MUTATES_THIS}) *
  3. Are any arguments mutated? ({@code MUTATES_ARGUMENTS}) *
  4. Does the call throw an error? ({@code THROWS}) *
  5. Is the return an escaped (mutable by other code) value? ({@code ESCAPED_RETURN}) *
* * @author [email protected] (John Lenz) */ public static final class SideEffectFlags { public static final int MUTATES_GLOBAL_STATE = 1; public static final int MUTATES_THIS = 2; public static final int MUTATES_ARGUMENTS = 4; public static final int THROWS = 8; public static final int ESCAPED_RETURN = 16; private static final int USED_BITS_MASK = (1 << 5) - 1; // TODO(nickreid): Delete one of these values. They should be symmetric with respect to // inversion. We need better noenclature to describe ESCAPED_RETURN. public static final int NO_SIDE_EFFECTS = ESCAPED_RETURN; public static final int ALL_SIDE_EFFECTS = MUTATES_GLOBAL_STATE | MUTATES_THIS | MUTATES_ARGUMENTS | THROWS | ESCAPED_RETURN; // A bitfield indicating the flag statuses. All bits set to 1 means "global state changes and // unknown locality of result". private int value = ALL_SIDE_EFFECTS; public SideEffectFlags() { } public SideEffectFlags(int value) { this.value = value; } public int valueOf() { return value; } /** All side-effect occur and the returned results are non-local. */ public SideEffectFlags setAllFlags() { value = ALL_SIDE_EFFECTS; return this; } /** No side-effects occur and the returned results are local. */ public SideEffectFlags clearAllFlags() { value = NO_SIDE_EFFECTS & ~ESCAPED_RETURN; return this; } /** * Preserve the return result flag, but clear the others: * no global state change, no throws, no this change, no arguments change */ public void clearSideEffectFlags() { value &= ESCAPED_RETURN; } public SideEffectFlags setMutatesGlobalState() { // Modify global means everything must be assumed to be modified. value |= MUTATES_GLOBAL_STATE | MUTATES_ARGUMENTS | MUTATES_THIS; return this; } public SideEffectFlags setThrows() { value |= THROWS; return this; } public SideEffectFlags setMutatesThis() { value |= MUTATES_THIS; return this; } public SideEffectFlags setMutatesArguments() { value |= MUTATES_ARGUMENTS; return this; } public SideEffectFlags setReturnsTainted() { value |= ESCAPED_RETURN; return this; } @Override @DoNotCall // For debugging only. public String toString() { StringBuilder builder = new StringBuilder("Side effects: "); if ((value & MUTATES_THIS) != 0) { builder.append("this "); } if ((value & MUTATES_GLOBAL_STATE) != 0) { builder.append("global "); } if ((value & THROWS) != 0) { builder.append("throw "); } if ((value & MUTATES_ARGUMENTS) != 0) { builder.append("args "); } if ((value & ESCAPED_RETURN) != 0) { builder.append("return "); } return builder.toString(); } } /** * @return Whether the only side-effect is "modifies this" */ public final boolean isOnlyModifiesThisCall() { int consideredFlags = getSideEffectFlags(); consideredFlags |= SideEffectFlags.NO_SIDE_EFFECTS; // Set non-side-effect bits to 1. // TODO(nickreid): Delete this; check if MUTATES_THIS is actually set. This was left in to // maintain existing behaviour but it makes the name of this method misleading. consideredFlags &= ~SideEffectFlags.MUTATES_THIS; return consideredFlags == SideEffectFlags.NO_SIDE_EFFECTS; } /** * @return Whether the only side-effect is "modifies arguments" */ public final boolean isOnlyModifiesArgumentsCall() { int consideredFlags = getSideEffectFlags(); consideredFlags |= SideEffectFlags.NO_SIDE_EFFECTS; // Set non-side-effect bits to 1. // TODO(nickreid): Delete this; check if MUTATES_ARGUMENTS is actually set. This was left in to // maintain existing behaviour but it makes the name of this method misleading. consideredFlags &= ~SideEffectFlags.MUTATES_ARGUMENTS; return consideredFlags == SideEffectFlags.NO_SIDE_EFFECTS; } /** * Returns true if this node is a function or constructor call that * has no side effects. */ public final boolean isNoSideEffectsCall() { int consideredFlags = getSideEffectFlags(); consideredFlags |= SideEffectFlags.NO_SIDE_EFFECTS; // Set non-side-effect bits to 1. return consideredFlags == SideEffectFlags.NO_SIDE_EFFECTS; } /** * Returns true if this node is a function or constructor call that * returns a primitive or a local object (an object that has no other * references). */ public final boolean isLocalResultCall() { return !allBitsSet(getSideEffectFlags(), SideEffectFlags.ESCAPED_RETURN); } /** Returns true if this is a new/call that may mutate its arguments. */ public final boolean mayMutateArguments() { return allBitsSet(getSideEffectFlags(), SideEffectFlags.MUTATES_ARGUMENTS); } /** Returns true if this is a new/call that may mutate global state or throw. */ public final boolean mayMutateGlobalStateOrThrow() { return anyBitSet( getSideEffectFlags(), SideEffectFlags.MUTATES_GLOBAL_STATE | SideEffectFlags.THROWS); } /** Returns true iff all the set bits in {@code mask} are also set in {@code value}. */ private static boolean allBitsSet(int value, int mask) { return (value & mask) == mask; } /** Returns true iff any the bit set in {@code mask} is also set in {@code value}. */ private static boolean anyBitSet(int value, int mask) { return (value & mask) != 0; } /** * This should only be called for STRING nodes children of OBJECTLIT. */ public boolean isQuotedString() { return false; } /** * This should only be called for STRING nodes children of OBJECTLIT. */ public void setQuotedString() { throw new IllegalStateException(this + " is not a StringNode"); } /*** AST type check methods ***/ public final boolean isAdd() { return this.token == Token.ADD; } public final boolean isSub() { return this.token == Token.SUB; } public final boolean isAnd() { return this.token == Token.AND; } public final boolean isArrayLit() { return this.token == Token.ARRAYLIT; } public final boolean isArrayPattern() { return this.token == Token.ARRAY_PATTERN; } public final boolean isAssign() { return this.token == Token.ASSIGN; } public final boolean isAssignAdd() { return this.token == Token.ASSIGN_ADD; } public final boolean isNormalBlock() { return isBlock(); } public final boolean isBlock() { return this.token == Token.BLOCK; } public final boolean isRoot() { return this.token == Token.ROOT; } public final boolean isAwait() { return this.token == Token.AWAIT; } public final boolean isBreak() { return this.token == Token.BREAK; } public final boolean isCall() { return this.token == Token.CALL; } public final boolean isCase() { return this.token == Token.CASE; } public final boolean isCast() { return this.token == Token.CAST; } public final boolean isCatch() { return this.token == Token.CATCH; } public final boolean isClass() { return this.token == Token.CLASS; } public final boolean isClassMembers() { return this.token == Token.CLASS_MEMBERS; } public final boolean isComma() { return this.token == Token.COMMA; } public final boolean isComputedProp() { return this.token == Token.COMPUTED_PROP; } public final boolean isContinue() { return this.token == Token.CONTINUE; } public final boolean isConst() { return this.token == Token.CONST; } public final boolean isDebugger() { return this.token == Token.DEBUGGER; } public final boolean isDec() { return this.token == Token.DEC; } public final boolean isDefaultCase() { return this.token == Token.DEFAULT_CASE; } public final boolean isDefaultValue() { return this.token == Token.DEFAULT_VALUE; } public final boolean isDelProp() { return this.token == Token.DELPROP; } public final boolean isDestructuringLhs() { return this.token == Token.DESTRUCTURING_LHS; } public final boolean isDestructuringPattern() { return isObjectPattern() || isArrayPattern(); } public final boolean isDo() { return this.token == Token.DO; } public final boolean isEmpty() { return this.token == Token.EMPTY; } public final boolean isExport() { return this.token == Token.EXPORT; } public final boolean isExportSpec() { return this.token == Token.EXPORT_SPEC; } public final boolean isExportSpecs() { return this.token == Token.EXPORT_SPECS; } public final boolean isExprResult() { return this.token == Token.EXPR_RESULT; } public final boolean isFalse() { return this.token == Token.FALSE; } public final boolean isVanillaFor() { return this.token == Token.FOR; } public final boolean isForIn() { return this.token == Token.FOR_IN; } public final boolean isForOf() { return this.token == Token.FOR_OF; } public final boolean isForAwaitOf() { return this.token == Token.FOR_AWAIT_OF; } public final boolean isFunction() { return this.token == Token.FUNCTION; } public final boolean isGetterDef() { return this.token == Token.GETTER_DEF; } public final boolean isGetElem() { return this.token == Token.GETELEM; } public final boolean isGetProp() { return this.token == Token.GETPROP; } public final boolean isHook() { return this.token == Token.HOOK; } public final boolean isIf() { return this.token == Token.IF; } public final boolean isImport() { return this.token == Token.IMPORT; } public final boolean isImportStar() { return this.token == Token.IMPORT_STAR; } public final boolean isImportSpec() { return this.token == Token.IMPORT_SPEC; } public final boolean isImportSpecs() { return this.token == Token.IMPORT_SPECS; } public final boolean isIn() { return this.token == Token.IN; } public final boolean isInc() { return this.token == Token.INC; } public final boolean isInstanceOf() { return this.token == Token.INSTANCEOF; } public final boolean isInterfaceMembers() { return this.token == Token.INTERFACE_MEMBERS; } public final boolean isRecordType() { return this.token == Token.RECORD_TYPE; } public final boolean isCallSignature() { return this.token == Token.CALL_SIGNATURE; } public final boolean isIndexSignature() { return this.token == Token.INDEX_SIGNATURE; } public final boolean isLabel() { return this.token == Token.LABEL; } public final boolean isLabelName() { return this.token == Token.LABEL_NAME; } public final boolean isLet() { return this.token == Token.LET; } public final boolean isMemberFunctionDef() { return this.token == Token.MEMBER_FUNCTION_DEF; } public final boolean isMemberVariableDef() { return this.token == Token.MEMBER_VARIABLE_DEF; } public final boolean isModuleBody() { return this.token == Token.MODULE_BODY; } public final boolean isName() { return this.token == Token.NAME; } public final boolean isNE() { return this.token == Token.NE; } public final boolean isNew() { return this.token == Token.NEW; } public final boolean isNot() { return this.token == Token.NOT; } public final boolean isNull() { return this.token == Token.NULL; } public final boolean isNumber() { return this.token == Token.NUMBER; } public final boolean isObjectLit() { return this.token == Token.OBJECTLIT; } public final boolean isObjectPattern() { return this.token == Token.OBJECT_PATTERN; } public final boolean isOr() { return this.token == Token.OR; } public final boolean isParamList() { return this.token == Token.PARAM_LIST; } public final boolean isRegExp() { return this.token == Token.REGEXP; } public final boolean isRest() { return this.token == Token.REST; } public final boolean isReturn() { return this.token == Token.RETURN; } public final boolean isScript() { return this.token == Token.SCRIPT; } public final boolean isSetterDef() { return this.token == Token.SETTER_DEF; } public final boolean isSpread() { return this.token == Token.SPREAD; } public final boolean isString() { return this.token == Token.STRING; } public final boolean isStringKey() { return this.token == Token.STRING_KEY; } public final boolean isSuper() { return this.token == Token.SUPER; } public final boolean isSwitch() { return this.token == Token.SWITCH; } public final boolean isTaggedTemplateLit() { return this.token == Token.TAGGED_TEMPLATELIT; } public final boolean isTemplateLit() { return this.token == Token.TEMPLATELIT; } public final boolean isTemplateLitString() { return this.token == Token.TEMPLATELIT_STRING; } public final boolean isTemplateLitSub() { return this.token == Token.TEMPLATELIT_SUB; } public final boolean isThis() { return this.token == Token.THIS; } public final boolean isThrow() { return this.token == Token.THROW; } public final boolean isTrue() { return this.token == Token.TRUE; } public final boolean isTry() { return this.token == Token.TRY; } public final boolean isTypeOf() { return this.token == Token.TYPEOF; } public final boolean isVar() { return this.token == Token.VAR; } public final boolean isVoid() { return this.token == Token.VOID; } public final boolean isWhile() { return this.token == Token.WHILE; } public final boolean isWith() { return this.token == Token.WITH; } public final boolean isYield() { return this.token == Token.YIELD; } // see writeObject() and readObject() for how this field is used in (de)serialization. // TODO(bradfordcsmith): We are assuming that we will never have multiple (de)serializations // happening at the same time. private static List incompleteNodes = null; @GwtIncompatible("ObjectOutputStream") private void writeObject(java.io.ObjectOutputStream out) throws Exception { // Do not call out.defaultWriteObject() as all the fields are transient and this class does not // have a superclass. checkState(Token.values().length < Byte.MAX_VALUE - Byte.MIN_VALUE); out.writeByte(token.ordinal()); writeEncodedInt(out, sourcePosition); writeEncodedInt(out, length); boolean isStartingNode = false; if (incompleteNodes == null) { // The first node to get serialized is responsible for completing serialization // of all the other nodes whose serialization it triggers. // This allows us to avoid deep recursion that would happen otherwise due to // node -> type obj -> another node -> type obj... isStartingNode = true; incompleteNodes = new ArrayList<>(); } incompleteNodes.add(this); // Serialize the embedded children linked list here to limit the depth of recursion (and avoid // serializing redundant information like the previous reference) Node currentChild = first; while (currentChild != null) { out.writeObject(currentChild); currentChild = currentChild.next; } // Null marks the end of the children. out.writeObject(null); out.writeObject(propListHead); if (isStartingNode) { List nodeList = Node.incompleteNodes; Node.incompleteNodes = null; for (Node n : nodeList) { out.writeObject(n.jstype); } } } @GwtIncompatible("ObjectInputStream") private void readObject(java.io.ObjectInputStream in) throws Exception { // Do not call in.defaultReadObject() as all the fields are transient and this class does not // have a superclass. token = Token.values()[in.readUnsignedByte()]; sourcePosition = readEncodedInt(in); length = readEncodedInt(in); boolean isStartingNode = false; if (incompleteNodes == null) { // The first node to get deserialized is responsible for completing deserialization // of all the other nodes whose deserialization it triggers. // This allows us to avoid deep recursion that would happen otherwise due to // node -> type obj -> another node -> type obj... isStartingNode = true; incompleteNodes = new ArrayList<>(); } incompleteNodes.add(this); // Deserialize the children list restoring the value of the previous reference. first = (Node) in.readObject(); if (first != null) { checkState(first.parent == null); first.parent = this; Node currentChild; Node lastChild = first; while ((currentChild = (Node) in.readObject()) != null) { checkState(currentChild.parent == null); currentChild.parent = this; // previous is never null, either it points to the previous sibling or if it is the first // sibling it points to the last one. checkState(currentChild.previous == null); currentChild.previous = lastChild; checkState(lastChild.next == null); lastChild.next = currentChild; lastChild = currentChild; } // Close the reverse circular list. checkState(first.previous == null); first.previous = lastChild; } propListHead = (PropListItem) in.readObject(); if (isStartingNode) { List nodeList = Node.incompleteNodes; Node.incompleteNodes = null; for (Node n : nodeList) { n.jstype = (JSType) in.readObject(); } } } /** * Encode integers using variable length encoding. * * Encodes an integer as a sequence of 7-bit values with a continuation bit. For example the * number 3912 (0111 0100 1000) is encoded in two bytes as follows 0xC80E (1100 1000 0000 1110), * i.e. first byte will be the lower 7 bits with a continuation bit set and second byte will * consist of the upper 7 bits with the continuation bit unset. * * This encoding aims to reduce the serialized footprint for the most common values, reducing the * footprint for all positive values that are smaller than 2^21 (~2000000): * 0 - 127 are encoded in one byte * 128 - 16384 are encoded in two bytes * 16385 - 2097152 are encoded in three bytes * 2097153 - 268435456 are encoded in four bytes. * values greater than 268435456 and negative values are encoded in 5 bytes. * * Most values for the length field will be encoded with one byte and most values for * sourcePosition will be encoded with 2 or 3 bytes. (Value -1, which is used to mark absence will * use 5 bytes, and could be accommodated in the present scheme by an offset of 1 if it leads to * size improvements). */ @GwtIncompatible("ObjectOutput") private void writeEncodedInt(ObjectOutput out, int value) throws IOException { while (value > 0X7f || value < 0) { out.writeByte(((value & 0X7f) | 0x80)); value >>>= 7; } out.writeByte(value); } @GwtIncompatible("ObjectInput") private int readEncodedInt(ObjectInput in) throws IOException { int value = 0; int shift = 0; byte current; while ((current = in.readByte()) < 0) { value |= (current & 0x7f) << shift; shift += 7; } value |= current << shift; return value; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy