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

com.google.javascript.jscomp.parsing.IRFactory Maven / Gradle / Ivy

/*
 * Copyright 2013 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.javascript.jscomp.parsing;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.rhino.TypeDeclarationsIR.anyType;
import static com.google.javascript.rhino.TypeDeclarationsIR.arrayType;
import static com.google.javascript.rhino.TypeDeclarationsIR.booleanType;
import static com.google.javascript.rhino.TypeDeclarationsIR.functionType;
import static com.google.javascript.rhino.TypeDeclarationsIR.namedType;
import static com.google.javascript.rhino.TypeDeclarationsIR.numberType;
import static com.google.javascript.rhino.TypeDeclarationsIR.parameterizedType;
import static com.google.javascript.rhino.TypeDeclarationsIR.stringType;
import static com.google.javascript.rhino.TypeDeclarationsIR.undefinedType;
import static com.google.javascript.rhino.TypeDeclarationsIR.unionType;
import static com.google.javascript.rhino.TypeDeclarationsIR.voidType;
import static java.lang.Integer.parseInt;
import static java.lang.Math.min;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.UnmodifiableIterator;
import com.google.javascript.jscomp.parsing.Config.JsDocParsing;
import com.google.javascript.jscomp.parsing.Config.LanguageMode;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.jscomp.parsing.parser.IdentifierToken;
import com.google.javascript.jscomp.parsing.parser.LiteralToken;
import com.google.javascript.jscomp.parsing.parser.TemplateLiteralToken;
import com.google.javascript.jscomp.parsing.parser.TokenType;
import com.google.javascript.jscomp.parsing.parser.trees.AmbientDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.ArgumentListTree;
import com.google.javascript.jscomp.parsing.parser.trees.ArrayLiteralExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.ArrayPatternTree;
import com.google.javascript.jscomp.parsing.parser.trees.ArrayTypeTree;
import com.google.javascript.jscomp.parsing.parser.trees.AwaitExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.BinaryOperatorTree;
import com.google.javascript.jscomp.parsing.parser.trees.BlockTree;
import com.google.javascript.jscomp.parsing.parser.trees.BreakStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.CallExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.CallSignatureTree;
import com.google.javascript.jscomp.parsing.parser.trees.CaseClauseTree;
import com.google.javascript.jscomp.parsing.parser.trees.CatchTree;
import com.google.javascript.jscomp.parsing.parser.trees.ClassDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.CommaExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.Comment;
import com.google.javascript.jscomp.parsing.parser.trees.ComprehensionForTree;
import com.google.javascript.jscomp.parsing.parser.trees.ComprehensionIfTree;
import com.google.javascript.jscomp.parsing.parser.trees.ComprehensionTree;
import com.google.javascript.jscomp.parsing.parser.trees.ComputedPropertyDefinitionTree;
import com.google.javascript.jscomp.parsing.parser.trees.ComputedPropertyGetterTree;
import com.google.javascript.jscomp.parsing.parser.trees.ComputedPropertyMemberVariableTree;
import com.google.javascript.jscomp.parsing.parser.trees.ComputedPropertyMethodTree;
import com.google.javascript.jscomp.parsing.parser.trees.ComputedPropertySetterTree;
import com.google.javascript.jscomp.parsing.parser.trees.ConditionalExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.ContinueStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.DebuggerStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.DefaultClauseTree;
import com.google.javascript.jscomp.parsing.parser.trees.DefaultParameterTree;
import com.google.javascript.jscomp.parsing.parser.trees.DoWhileStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.DynamicImportTree;
import com.google.javascript.jscomp.parsing.parser.trees.EmptyStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.EnumDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.ExportDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.ExportSpecifierTree;
import com.google.javascript.jscomp.parsing.parser.trees.ExpressionStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.FinallyTree;
import com.google.javascript.jscomp.parsing.parser.trees.ForAwaitOfStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.ForInStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.ForOfStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.ForStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.FormalParameterListTree;
import com.google.javascript.jscomp.parsing.parser.trees.FunctionDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.FunctionTypeTree;
import com.google.javascript.jscomp.parsing.parser.trees.GenericTypeListTree;
import com.google.javascript.jscomp.parsing.parser.trees.GetAccessorTree;
import com.google.javascript.jscomp.parsing.parser.trees.IdentifierExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.IfStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.ImportDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.ImportMetaExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.ImportSpecifierTree;
import com.google.javascript.jscomp.parsing.parser.trees.IndexSignatureTree;
import com.google.javascript.jscomp.parsing.parser.trees.InterfaceDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.IterRestTree;
import com.google.javascript.jscomp.parsing.parser.trees.IterSpreadTree;
import com.google.javascript.jscomp.parsing.parser.trees.LabelledStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.LiteralExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.MemberExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.MemberLookupExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.MemberVariableTree;
import com.google.javascript.jscomp.parsing.parser.trees.MissingPrimaryExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.NamespaceDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.NamespaceNameTree;
import com.google.javascript.jscomp.parsing.parser.trees.NewExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.NewTargetExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.NullTree;
import com.google.javascript.jscomp.parsing.parser.trees.ObjectLiteralExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.ObjectPatternTree;
import com.google.javascript.jscomp.parsing.parser.trees.ObjectSpreadTree;
import com.google.javascript.jscomp.parsing.parser.trees.OptionalCallExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.OptionalMemberExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.OptionalMemberLookupExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.OptionalParameterTree;
import com.google.javascript.jscomp.parsing.parser.trees.ParameterizedTypeTree;
import com.google.javascript.jscomp.parsing.parser.trees.ParenExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.ParseTree;
import com.google.javascript.jscomp.parsing.parser.trees.ParseTreeType;
import com.google.javascript.jscomp.parsing.parser.trees.ProgramTree;
import com.google.javascript.jscomp.parsing.parser.trees.PropertyNameAssignmentTree;
import com.google.javascript.jscomp.parsing.parser.trees.RecordTypeTree;
import com.google.javascript.jscomp.parsing.parser.trees.ReturnStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.SetAccessorTree;
import com.google.javascript.jscomp.parsing.parser.trees.SuperExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.SwitchStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.TemplateLiteralExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.TemplateLiteralPortionTree;
import com.google.javascript.jscomp.parsing.parser.trees.TemplateSubstitutionTree;
import com.google.javascript.jscomp.parsing.parser.trees.ThisExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.ThrowStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.TryStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.TypeAliasTree;
import com.google.javascript.jscomp.parsing.parser.trees.TypeNameTree;
import com.google.javascript.jscomp.parsing.parser.trees.TypeQueryTree;
import com.google.javascript.jscomp.parsing.parser.trees.TypedParameterTree;
import com.google.javascript.jscomp.parsing.parser.trees.UnaryExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.UnionTypeTree;
import com.google.javascript.jscomp.parsing.parser.trees.UpdateExpressionTree;
import com.google.javascript.jscomp.parsing.parser.trees.UpdateExpressionTree.OperatorPosition;
import com.google.javascript.jscomp.parsing.parser.trees.VariableDeclarationListTree;
import com.google.javascript.jscomp.parsing.parser.trees.VariableDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.VariableStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.WhileStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.WithStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.YieldExpressionTree;
import com.google.javascript.jscomp.parsing.parser.util.SourcePosition;
import com.google.javascript.jscomp.parsing.parser.util.SourceRange;
import com.google.javascript.jscomp.parsing.parser.util.format.SimpleFormat;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfo.Visibility;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Node.TypeDeclarationNode;
import com.google.javascript.rhino.NonJSDocComment;
import com.google.javascript.rhino.StaticSourceFile;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import com.google.javascript.rhino.dtoa.DToA;
import java.math.BigInteger;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * IRFactory transforms the external AST to the internal AST.
 */
class IRFactory {

  static final String GETTER_ERROR_MESSAGE =
      "getters are not supported in older versions of JavaScript. " +
      "If you are targeting newer versions of JavaScript, " +
      "set the appropriate language_in option.";

  static final String SETTER_ERROR_MESSAGE =
      "setters are not supported in older versions of JavaScript. " +
      "If you are targeting newer versions of JavaScript, " +
      "set the appropriate language_in option.";

  static final String INVALID_ES3_PROP_NAME =
      "Keywords and reserved words are not allowed as unquoted property " +
      "names in older versions of JavaScript. " +
      "If you are targeting newer versions of JavaScript, " +
      "set the appropriate language_in option.";

  static final String INVALID_ES5_STRICT_OCTAL =
      "Octal integer literals are not supported in strict mode.";

  static final String INVALID_OCTAL_DIGIT =
      "Invalid octal digit in octal literal.";

  static final String STRING_CONTINUATION_WARNING =
      "String continuations are not recommended. See"
      + " https://google.github.io/styleguide/jsguide.html#features-strings-no-line-continuations";

  static final String OCTAL_STRING_LITERAL_WARNING =
      "Octal literals in strings are not supported in this language mode.";

  static final String DUPLICATE_PARAMETER =
      "Duplicate parameter name \"%s\"";

  static final String DUPLICATE_LABEL =
      "Duplicate label \"%s\"";

  static final String UNLABELED_BREAK =
      "unlabelled break must be inside loop or switch";

  static final String UNEXPECTED_CONTINUE = "continue must be inside loop";

  static final String UNEXPECTED_LABELLED_CONTINUE =
      "continue can only use labeles of iteration statements";

  static final String UNEXPECTED_RETURN = "return must be inside function";
  static final String UNEXPECTED_NEW_DOT_TARGET = "new.target must be inside a function";
  static final String UNDEFINED_LABEL = "undefined label \"%s\"";

  private final String sourceString;
  private final StaticSourceFile sourceFile;
  private final String sourceName;
  private final Config config;
  private final ErrorReporter errorReporter;
  private final TransformDispatcher transformDispatcher;

  private static final ImmutableSet USE_STRICT_ONLY = ImmutableSet.of("use strict");

  private static final ImmutableSet ALLOWED_DIRECTIVES =
      USE_STRICT_ONLY;

  private static final ImmutableSet ES5_RESERVED_KEYWORDS =
      ImmutableSet.of(
          // From Section 7.6.1.2
          "class", "const", "enum", "export", "extends", "import", "super");
  private static final ImmutableSet ES5_STRICT_RESERVED_KEYWORDS =
      ImmutableSet.of(
          // From Section 7.6.1.2
          "class", "const", "enum", "export", "extends", "import", "super",
          "implements", "interface", "let", "package", "private", "protected",
          "public", "static", "yield");

  /**
   * If non-null, use this set of keywords instead of TokenStream.isKeyword().
   */
  @Nullable
  private final Set reservedKeywords;
  private final Set parsedComments = new HashSet<>();

  // @license text gets appended onto the fileLevelJsDocBuilder as found,
  // and stored in JSDocInfo for placeholder node.
  JSDocInfoBuilder fileLevelJsDocBuilder;
  JSDocInfo fileOverviewInfo = null;

  // Use a template node for properties set on all nodes to minimize the
  // memory footprint associated with these.
  private final Node templateNode;

  private final UnmodifiableIterator nextCommentIter;
  private final UnmodifiableIterator nextNonJSDocCommentIter;

  private Comment currentComment;
  private Comment currentNonJSDocComment;

  private boolean currentFileIsExterns = false;
  private boolean hasJsDocTypeAnnotations = false;

  private FeatureSet features = FeatureSet.BARE_MINIMUM;
  private Node resultNode;

  private IRFactory(String sourceString,
                    StaticSourceFile sourceFile,
                    Config config,
                    ErrorReporter errorReporter,
                    ImmutableList comments) {
    this.sourceString = sourceString;
    this.nextCommentIter = comments.iterator();
    this.nextNonJSDocCommentIter = comments.iterator();
    this.currentComment = skipNonJsDoc(nextCommentIter);
    this.currentNonJSDocComment = skipJsDocComments(nextNonJSDocCommentIter);
    this.sourceFile = sourceFile;
    // The template node properties are applied to all nodes in this transform.
    this.templateNode = createTemplateNode();

    this.fileLevelJsDocBuilder =
        new JSDocInfoBuilder(config.jsDocParsingMode().shouldParseDescriptions());

    // Sometimes this will be null in tests.
    this.sourceName = sourceFile == null ? null : sourceFile.getName();

    this.config = config;
    this.errorReporter = errorReporter;
    this.transformDispatcher = new TransformDispatcher();

    if (config.strictMode().isStrict()) {
      reservedKeywords = ES5_STRICT_RESERVED_KEYWORDS;
    } else if (config.languageMode() == LanguageMode.ECMASCRIPT3) {
      reservedKeywords = null; // use TokenStream.isKeyword instead
    } else {
      reservedKeywords = ES5_RESERVED_KEYWORDS;
    }
  }

  private static Comment skipNonJsDoc(UnmodifiableIterator comments) {
    while (comments.hasNext()) {
      Comment comment = comments.next();
      if (comment.type == Comment.Type.JSDOC) {
        return comment;
      }
    }
    return null;
  }

  // Create a template node to use as a source of common attributes, this allows
  // the prop structure to be shared among all the node from this source file.
  // This reduces the cost of these properties to O(nodes) to O(files).
  private Node createTemplateNode() {
    // The Node type choice is arbitrary.
    Node templateNode = new Node(Token.SCRIPT);
    templateNode.setStaticSourceFile(sourceFile);
    return templateNode;
  }

  public static IRFactory transformTree(ProgramTree tree,
                                        StaticSourceFile sourceFile,
                                        String sourceString,
                                        Config config,
                                        ErrorReporter errorReporter) {
    IRFactory irFactory = new IRFactory(sourceString, sourceFile,
        config, errorReporter, tree.sourceComments);

    // don't call transform as we don't want standard jsdoc handling.
    Node n = irFactory.transformDispatcher.process(tree);
    irFactory.setSourceInfo(n, tree);

    if (tree.sourceComments != null) {
      for (Comment comment : tree.sourceComments) {
        if ((comment.type == Comment.Type.JSDOC || comment.type == Comment.Type.IMPORTANT)
            && !irFactory.parsedComments.contains(comment)) {
          irFactory.handlePossibleFileOverviewJsDoc(comment);
        }
      }
    }

    irFactory.setFileOverviewJsDoc(n);

    irFactory.validateAll(n);
    irFactory.resultNode = n;

    return irFactory;
  }

  Node getResultNode() {
    return resultNode;
  }

  FeatureSet getFeatures() {
    return features;
  }

  private void validateAll(Node n) {
    ArrayDeque work = new ArrayDeque<>();
    while (n != null) {
      validate(n);
      Node nextSibling = n.getNext();
      Node firstChild = n.getFirstChild();
      if (firstChild != null) {
        if (nextSibling != null) {
          // handle the siblings later
          work.push(nextSibling);
        }
        n = firstChild;
      } else if (nextSibling != null) {
        // no children, handle the next sibling
        n = nextSibling;
      } else {
        // no siblings, continue with work we have saved on the work queue
        n = work.poll();
      }
    }
    checkState(work.isEmpty());
  }

  private void validate(Node n) {
    validateParameters(n);
    validateBreakContinue(n);
    validateReturn(n);
    validateNewDotTarget(n);
    validateLabel(n);
    validateBlockScopedFunctions(n);
  }

  private void validateReturn(Node n) {
    if (n.isReturn()) {
      Node parent = n;
      while ((parent = parent.getParent()) != null) {
        if (parent.isFunction()) {
          return;
        }
      }
      errorReporter.error(UNEXPECTED_RETURN,
          sourceName, n.getLineno(), n.getCharno());
    }
  }

  private void validateNewDotTarget(Node n) {
    if (n.getToken() == Token.NEW_TARGET) {
      Node parent = n;
      while ((parent = parent.getParent()) != null) {
        if (parent.isFunction()) {
          return;
        }
      }
      errorReporter.error(UNEXPECTED_NEW_DOT_TARGET, sourceName, n.getLineno(), n.getCharno());
    }
  }

  private void validateBreakContinue(Node n) {
    if (n.isBreak() || n.isContinue()) {
      Node labelName = n.getFirstChild();
      if (labelName != null) {
        Node parent = n.getParent();
        while (!parent.isLabel() || !labelsMatch(parent, labelName)) {
          if (parent.isFunction() || parent.isScript()) {
            // report missing label
            errorReporter.error(
                SimpleFormat.format(UNDEFINED_LABEL, labelName.getString()),
                sourceName,
                n.getLineno(), n.getCharno());
            break;
          }
          parent = parent.getParent();
        }
        if (parent.isLabel() && labelsMatch(parent, labelName)) {
          if (n.isContinue() && !isContinueTarget(parent.getLastChild())) {
            // report invalid continue target
            errorReporter.error(
                UNEXPECTED_LABELLED_CONTINUE,
                sourceName,
                n.getLineno(), n.getCharno());
          }
        }
      } else {
        if (n.isContinue()) {
          Node parent = n.getParent();
          while (!isContinueTarget(parent)) {
            if (parent.isFunction() || parent.isScript()) {
              // report invalid continue
              errorReporter.error(
                  UNEXPECTED_CONTINUE,
                  sourceName,
                  n.getLineno(), n.getCharno());
              break;
            }
            parent = parent.getParent();
          }
        } else {
          Node parent = n.getParent();
          while (!isBreakTarget(parent)) {
            if (parent.isFunction() || parent.isScript()) {
              // report invalid break
              errorReporter.error(
                  UNLABELED_BREAK,
                  sourceName,
                  n.getLineno(), n.getCharno());
              break;
            }
            parent = parent.getParent();
          }
        }
      }
    }
  }

  private static boolean isBreakTarget(Node n) {
    switch (n.getToken()) {
      case FOR:
      case FOR_IN:
      case FOR_OF:
      case FOR_AWAIT_OF:
      case WHILE:
      case DO:
      case SWITCH:
        return true;
      default:
        return false;
    }
  }

  private static boolean isContinueTarget(Node n) {
    switch (n.getToken()) {
      case FOR:
      case FOR_IN:
      case FOR_OF:
      case FOR_AWAIT_OF:
      case WHILE:
      case DO:
        return true;
      default:
        return false;
    }
  }


  private static boolean labelsMatch(Node label, Node labelName) {
    return label.getFirstChild().getString().equals(labelName.getString());
  }

  private void validateLabel(Node n) {
    if (n.isLabel()) {
      Node labelName = n.getFirstChild();
      for (Node parent = n.getParent();
           parent != null && !parent.isFunction(); parent = parent.getParent()) {
        if (parent.isLabel() && labelsMatch(parent, labelName)) {
          errorReporter.error(
              SimpleFormat.format(DUPLICATE_LABEL, labelName.getString()),
              sourceName,
              n.getLineno(), n.getCharno());
          break;
        }
      }
    }
  }

  private void validateParameters(Node n) {
    if (n.isParamList()) {
      Set seenNames = new LinkedHashSet<>();
      for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
        ParsingUtil.getParamOrPatternNames(
            c,
            (Node param) -> {
              String paramName = param.getString();
              if (!seenNames.add(paramName)) {
                errorReporter.warning(
                    SimpleFormat.format(DUPLICATE_PARAMETER, paramName),
                    sourceName,
                    param.getLineno(),
                    param.getCharno());
              }
            });
      }
    }
  }

  private void validateBlockScopedFunctions(Node n) {
    if (n.isFunction() && n.getParent().isBlock() && !n.getGrandparent().isFunction()) {
      maybeWarnForFeature(n, Feature.BLOCK_SCOPED_FUNCTION_DECLARATION);
    }
  }

  JSDocInfo recordJsDoc(SourceRange location, JSDocInfo info) {
    if (info != null && info.hasTypeInformation()) {
      hasJsDocTypeAnnotations = true;
      if (features.version().equals("ts")) {
        errorReporter.error("Can only have JSDoc or inline type annotations, not both",
            sourceName, lineno(location.start), charno(location.start));
      }
    }
    return info;
  }

  void recordTypeSyntax(SourceRange location) {
    if (hasJsDocTypeAnnotations) {
      errorReporter.error("Can only have JSDoc or inline type annotations, not both",
          sourceName, lineno(location.start), charno(location.start));
    }
  }

  private void setFileOverviewJsDoc(Node irNode) {
    // Only after we've seen all @fileoverview entries, attach the
    // last one to the root node, and copy the found license strings
    // to that node.
    JSDocInfo rootNodeJsDoc = fileLevelJsDocBuilder.build();
    if (rootNodeJsDoc != null) {
      irNode.setJSDocInfo(rootNodeJsDoc);
    }

    if (fileOverviewInfo != null) {
      if ((irNode.getJSDocInfo() != null) &&
          (irNode.getJSDocInfo().getLicense() != null)) {
        JSDocInfoBuilder builder = JSDocInfoBuilder.copyFrom(fileOverviewInfo);
        builder.recordLicense(irNode.getJSDocInfo().getLicense());
        fileOverviewInfo = builder.build();
      }
      irNode.setJSDocInfo(fileOverviewInfo);
    }
  }

  Node transformBlock(ParseTree node) {
    Node irNode = transform(node);
    if (!irNode.isBlock()) {
      if (irNode.isEmpty()) {
        irNode.setToken(Token.BLOCK);
      } else {
        Node newBlock = newNode(Token.BLOCK, irNode);
        setSourceInfo(newBlock, irNode);
        irNode = newBlock;
      }
      irNode.setIsAddedBlock(true);
    }
    return irNode;
  }

  /**
   * @return true if the jsDocParser represents a fileoverview.
   */
  private boolean handlePossibleFileOverviewJsDoc(
      JsDocInfoParser jsDocParser) {
    if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) {
      fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo();
      if (fileOverviewInfo.isExterns()) {
        this.currentFileIsExterns = true;
      }
      return true;
    }
    return false;
  }

  private void handlePossibleFileOverviewJsDoc(Comment comment) {
    JsDocInfoParser jsDocParser = createJsDocInfoParser(comment);
    parsedComments.add(comment);
    handlePossibleFileOverviewJsDoc(jsDocParser);
  }

  private Comment getJsDoc(SourceRange location) {
    Comment closestPreviousComment = null;
    while (hasPendingCommentBefore(location)) {
      closestPreviousComment = currentComment;
      currentComment = skipNonJsDoc(nextCommentIter);
    }

    return closestPreviousComment;
  }

  private Comment getJsDoc(ParseTree tree) {
    return getJsDoc(tree.location);
  }

  private Comment getJsDoc(
      com.google.javascript.jscomp.parsing.parser.Token token) {
    return getJsDoc(token.location);
  }

  private boolean hasPendingCommentBefore(SourceRange location) {
    return currentComment != null
        && currentComment.location.end.offset <= location.start.offset;
  }

  private boolean hasPendingCommentBefore(ParseTree tree) {
    return hasPendingCommentBefore(tree.location);
  }

  private JSDocInfo handleJsDoc(Comment comment) {
    if (comment != null) {
      JsDocInfoParser jsDocParser = createJsDocInfoParser(comment);
      parsedComments.add(comment);
      if (!handlePossibleFileOverviewJsDoc(jsDocParser)) {
        return recordJsDoc(comment.location,
            jsDocParser.retrieveAndResetParsedJSDocInfo());
      }
    }
    return null;
  }

  private JSDocInfo handleJsDoc(ParseTree node) {
    if (!shouldAttachJSDocHere(node)) {
      return null;
    }
    return handleJsDoc(getJsDoc(node));
  }

  JSDocInfo handleJsDoc(com.google.javascript.jscomp.parsing.parser.Token token) {
    return handleJsDoc(getJsDoc(token));
  }

  private boolean shouldAttachJSDocHere(ParseTree tree) {
    switch (tree.type) {
      case EXPRESSION_STATEMENT:
      case LABELLED_STATEMENT:
      case EXPORT_DECLARATION:
      case TEMPLATE_SUBSTITUTION:
        return false;
      case CALL_EXPRESSION:
      case CONDITIONAL_EXPRESSION:
      case BINARY_OPERATOR:
      case MEMBER_EXPRESSION:
      case MEMBER_LOOKUP_EXPRESSION:
      case UPDATE_EXPRESSION:
        ParseTree nearest = findNearestNode(tree);
        if (nearest.type == ParseTreeType.PAREN_EXPRESSION) {
          return false;
        }
        return true;
      default:
        return true;
    }
  }

  /**
   * Appends every comment associated with this node into one NonJSDocComment. It would be legal to
   * replace all comments associated with this node with that one string.
   *
   * @param comments - list of line or block comments that are sequential in source code
   * @return complete comment as NonJSDocComment
   */
  private static NonJSDocComment combineCommentsIntoSingleComment(ArrayList comments) {
    String result = "";
    Iterator itr = comments.iterator();
    int prevCommentEndLine = Integer.MAX_VALUE;
    int completeCommentBegin = Integer.MAX_VALUE;
    int completeCommentEnd = 0;
    while (itr.hasNext()) {
      Comment currComment = itr.next();
      if (currComment.location.start.offset < completeCommentBegin) {
        completeCommentBegin = currComment.location.start.offset;
      }
      if (currComment.location.end.offset > completeCommentEnd) {
        completeCommentEnd = currComment.location.end.offset;
      }
      while (prevCommentEndLine < currComment.location.start.line) {
        result += "\n";
        prevCommentEndLine++;
      }
      result += currComment.value;
      if (itr.hasNext()) {
        prevCommentEndLine = currComment.location.end.line;
      }
    }

    SourcePosition start = comments.get(0).location.start;
    SourcePosition end = Iterables.getLast(comments).location.end;

    NonJSDocComment nonJSDocComment = new NonJSDocComment(start, end, result);
    nonJSDocComment.setEndsAsLineComment(Iterables.getLast(comments).type == Comment.Type.LINE);
    return nonJSDocComment;
  }

  private static Comment skipJsDocComments(UnmodifiableIterator comments) {
    while (comments.hasNext()) {
      Comment comment = comments.next();
      if (comment.type == Comment.Type.LINE || comment.type == Comment.Type.BLOCK) {
        return comment;
      }
    }
    return null;
  }

  private boolean hasPendingNonJSDocCommentBefore(SourceRange location) {
    return currentNonJSDocComment != null
        && currentNonJSDocComment.location.end.offset <= location.start.offset;
  }

  private boolean hasPendingNonJSDocCommentBefore(SourcePosition pos) {
    return currentNonJSDocComment != null
        && currentNonJSDocComment.location.end.line <= pos.line
        && currentNonJSDocComment.location.end.offset <= pos.offset;
  }

  private ArrayList getNonJSDocComments(SourceRange location) {
    ArrayList previousComments = new ArrayList<>();
    while (hasPendingNonJSDocCommentBefore(location)) {
      previousComments.add(currentNonJSDocComment);
      currentNonJSDocComment = skipJsDocComments(nextNonJSDocCommentIter);
    }
    return previousComments;
  }

  private ArrayList getNonJSDocComments(
      com.google.javascript.jscomp.parsing.parser.Token token) {
    return getNonJSDocComments(token.location);
  }

  private ArrayList getNonJSDocComments(ParseTree tree) {
    return getNonJSDocComments(tree.location);
  }

  private ArrayList getNonJSDocCommentsBefore(SourcePosition pos) {
    ArrayList previousComments = new ArrayList<>();
    while (hasPendingNonJSDocCommentBefore(pos)) {
      previousComments.add(currentNonJSDocComment);
      currentNonJSDocComment = skipJsDocComments(nextNonJSDocCommentIter);
    }
    return previousComments;
  }

  private static ParseTree findNearestNode(ParseTree tree) {
    while (true) {
      switch (tree.type) {
        case EXPRESSION_STATEMENT:
          tree = tree.asExpressionStatement().expression;
          continue;
        case CALL_EXPRESSION:
          tree = tree.asCallExpression().operand;
          continue;
        case BINARY_OPERATOR:
          tree = tree.asBinaryOperator().left;
          continue;
        case CONDITIONAL_EXPRESSION:
          tree = tree.asConditionalExpression().condition;
          continue;
        case MEMBER_EXPRESSION:
          tree = tree.asMemberExpression().operand;
          continue;
        case MEMBER_LOOKUP_EXPRESSION:
          tree = tree.asMemberLookupExpression().operand;
          continue;
        case UPDATE_EXPRESSION:
          tree = tree.asUpdateExpression().operand;
          continue;
        default:
          return tree;
      }
    }
  }

  Node transform(ParseTree tree) {
    JSDocInfo info = handleJsDoc(tree);
    NonJSDocComment associatedNonJSDocComment = null;
    if (config.jsDocParsingMode() == JsDocParsing.INCLUDE_ALL_COMMENTS) {
      ArrayList nonJSDocComments = getNonJSDocComments(tree);
      if (!nonJSDocComments.isEmpty()) {
        associatedNonJSDocComment = combineCommentsIntoSingleComment(nonJSDocComments);
      }
    }
    Node node = transformDispatcher.process(tree);
    if (info != null) {
      node = maybeInjectCastNode(tree, info, node);
      node.setJSDocInfo(info);
    }
    if (this.config.jsDocParsingMode() == JsDocParsing.INCLUDE_ALL_COMMENTS) {
      if (associatedNonJSDocComment != null) {
        node.setNonJSDocComment(associatedNonJSDocComment);
      }
    }

    setSourceInfo(node, tree);
    return node;
  }

  private Node maybeInjectCastNode(ParseTree node, JSDocInfo info, Node irNode) {
    if (node.type == ParseTreeType.PAREN_EXPRESSION && info.hasType()) {
      irNode = newNode(Token.CAST, irNode);
    }
    return irNode;
  }

  /**
   * Names and destructuring patterns, in parameters or variable declarations are special, because
   * they can have inline type docs attached.
   *
   * 
function f(/** string */ x) {}
* * annotates 'x' as a string. * * @see Using Inline Doc * Comments */ Node transformNodeWithInlineComments(ParseTree tree) { JSDocInfo info = handleInlineJsDoc(tree); NonJSDocComment associatedNonJSDocComment = null; if (config.jsDocParsingMode() == JsDocParsing.INCLUDE_ALL_COMMENTS) { ArrayList nonJSDocComments = getNonJSDocComments(tree); if (!nonJSDocComments.isEmpty()) { associatedNonJSDocComment = combineCommentsIntoSingleComment(nonJSDocComments); associatedNonJSDocComment.setIsInline(true); } } Node node = transformDispatcher.process(tree); if (info != null) { node.setJSDocInfo(info); } if (this.config.jsDocParsingMode() == JsDocParsing.INCLUDE_ALL_COMMENTS) { if (associatedNonJSDocComment != null) { node.setNonJSDocComment(associatedNonJSDocComment); } } setSourceInfo(node, tree); return node; } JSDocInfo handleInlineJsDoc(ParseTree node) { return handleInlineJsDoc(node.location); } JSDocInfo handleInlineJsDoc( com.google.javascript.jscomp.parsing.parser.Token token) { return handleInlineJsDoc(token.location); } JSDocInfo handleInlineJsDoc(SourceRange location) { Comment comment = getJsDoc(location); if (comment != null && !comment.value.contains("@")) { return recordJsDoc(location, parseInlineTypeDoc(comment)); } else { return handleJsDoc(comment); } } Node transformNumberAsString(LiteralToken token) { Node irNode; if (token.type == TokenType.BIGINT) { BigInteger value = normalizeBigInt(token); irNode = newStringNode(value.toString()); } else { // must be NUMBER double value = normalizeNumber(token); irNode = newStringNode(DToA.numberToString(value)); } JSDocInfo jsDocInfo = handleJsDoc(token); if (jsDocInfo != null) { irNode.setJSDocInfo(jsDocInfo); } setSourceInfo(irNode, token); return irNode; } static int lineno(ParseTree node) { return lineno(node.location.start); } static int lineno(com.google.javascript.jscomp.parsing.parser.Token token) { return lineno(token.location.start); } static int lineno(SourcePosition location) { // location lines start at zero, our AST starts at 1. return location.line + 1; } static int charno(ParseTree node) { return charno(node.location.start); } static int charno(com.google.javascript.jscomp.parsing.parser.Token token) { return charno(token.location.start); } static int charno(SourcePosition location) { return location.column; } String languageFeatureWarningMessage(Feature feature) { LanguageMode forFeature = LanguageMode.minimumRequiredFor(feature); if (forFeature == LanguageMode.UNSUPPORTED) { return "This language feature is not currently supported by the compiler: " + feature; } else { return "This language feature is only supported for " + LanguageMode.minimumRequiredFor(feature) + " mode or better: " + feature; } } void maybeWarnForFeature(ParseTree node, Feature feature) { features = features.with(feature); if (!isSupportedForInputLanguageMode(feature)) { errorReporter.warning( languageFeatureWarningMessage(feature), sourceName, lineno(node), charno(node)); } } void maybeWarnForFeature( com.google.javascript.jscomp.parsing.parser.Token token, Feature feature) { features = features.with(feature); if (!isSupportedForInputLanguageMode(feature)) { errorReporter.warning( languageFeatureWarningMessage(feature), sourceName, lineno(token), charno(token)); } } void maybeWarnForFeature(Node node, Feature feature) { features = features.with(feature); if (!isSupportedForInputLanguageMode(feature)) { errorReporter.warning( languageFeatureWarningMessage(feature), sourceName, node.getLineno(), node.getCharno()); } } void setSourceInfo(Node node, Node ref) { node.setLineno(ref.getLineno()); node.setCharno(ref.getCharno()); setLengthFrom(node, ref); } void setSourceInfo(Node irNode, ParseTree node) { if (irNode.getLineno() == -1) { setSourceInfo(irNode, node.location.start, node.location.end); } } void setSourceInfo( Node irNode, com.google.javascript.jscomp.parsing.parser.Token token) { setSourceInfo(irNode, token.location.start, token.location.end); } void setSourceInfo( Node node, SourcePosition start, SourcePosition end) { if (node.getLineno() == -1) { // If we didn't already set the line, then set it now. This avoids // cases like ParenthesizedExpression where we just return a previous // node, but don't want the new node to get its parent's line number. int lineno = lineno(start); node.setLineno(lineno); int charno = charno(start); node.setCharno(charno); setLength(node, start, end); } } /** * Creates a JsDocInfoParser and parses the JsDoc string. * * Used both for handling individual JSDoc comments and for handling * file-level JSDoc comments (@fileoverview and @license). * * @param node The JsDoc Comment node to parse. * @return A JsDocInfoParser. Will contain either fileoverview JsDoc, or * normal JsDoc, or no JsDoc (if the method parses to the wrong level). */ private JsDocInfoParser createJsDocInfoParser(Comment node) { String comment = node.value; int lineno = lineno(node.location.start); int charno = charno(node.location.start); int position = node.location.start.offset; // The JsDocInfoParser expects the comment without the initial '/**'. int numOpeningChars = 3; JsDocInfoParser jsdocParser = new JsDocInfoParser( new JsDocTokenStream(comment.substring(numOpeningChars), lineno, charno + numOpeningChars), comment, position, templateNode, config, errorReporter); jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder); jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo); if (node.type == Comment.Type.IMPORTANT && node.value.length() > 0) { jsdocParser.parseImportantComment(); } else { jsdocParser.parse(); } return jsdocParser; } /** * Parses inline type info. */ private JSDocInfo parseInlineTypeDoc(Comment node) { String comment = node.value; int lineno = lineno(node.location.start); int charno = charno(node.location.start); // The JsDocInfoParser expects the comment without the initial '/**'. int numOpeningChars = 3; JsDocInfoParser parser = new JsDocInfoParser( new JsDocTokenStream(comment.substring(numOpeningChars), lineno, charno + numOpeningChars), comment, node.location.start.offset, templateNode, config, errorReporter); return parser.parseInlineTypeDoc(); } // Set the length on the node if we're in IDE mode. void setLength( Node node, SourcePosition start, SourcePosition end) { node.setLength(end.offset - start.offset); } void setLengthFrom(Node node, Node ref) { node.setLength(ref.getLength()); } private class TransformDispatcher { /** * Transforms the given node and then sets its type to Token.STRING if it * was Token.NAME. If its type was already Token.STRING, then quotes it. * Used for properties, as the old AST uses String tokens, while the new one * uses Name tokens for unquoted strings. For example, in * var o = {'a' : 1, b: 2}; * the string 'a' is quoted, while the name b is turned into a string, but * unquoted. */ private Node processObjectLitKeyAsString( com.google.javascript.jscomp.parsing.parser.Token token) { Node ret; if (token == null) { return createMissingExpressionNode(); } else if (token.type == TokenType.IDENTIFIER) { ret = processName(token.asIdentifier(), true); } else if (token.type == TokenType.NUMBER || token.type == TokenType.BIGINT) { ret = transformNumberAsString(token.asLiteral()); ret.putBooleanProp(Node.QUOTED_PROP, true); } else { ret = processString(token.asLiteral()); ret.putBooleanProp(Node.QUOTED_PROP, true); } checkState(ret.isString()); return ret; } Node processComprehension(ComprehensionTree tree) { return unsupportedLanguageFeature(tree, "array/generator comprehensions"); } Node processComprehensionFor(ComprehensionForTree tree) { return unsupportedLanguageFeature(tree, "array/generator comprehensions"); } Node processComprehensionIf(ComprehensionIfTree tree) { return unsupportedLanguageFeature(tree, "array/generator comprehensions"); } Node processArrayLiteral(ArrayLiteralExpressionTree tree) { Node node = newNode(Token.ARRAYLIT); node.setTrailingComma(tree.hasTrailingComma); for (ParseTree child : tree.elements) { Node c = transform(child); node.addChildToBack(c); } return node; } Node processArrayPattern(ArrayPatternTree tree) { maybeWarnForFeature(tree, Feature.ARRAY_DESTRUCTURING); Node node = newNode(Token.ARRAY_PATTERN); for (ParseTree child : tree.elements) { final Node elementNode; switch (child.type) { case DEFAULT_PARAMETER: // processDefaultParameter() knows how to find and apply inline JSDoc to the right node elementNode = processDefaultParameter(child.asDefaultParameter()); break; case ITER_REST: maybeWarnForFeature(child, Feature.ARRAY_PATTERN_REST); elementNode = transformNodeWithInlineComments(child); break; default: elementNode = transformNodeWithInlineComments(child); break; } node.addChildToBack(elementNode); } return node; } Node processObjectPattern(ObjectPatternTree tree) { maybeWarnForFeature(tree, Feature.OBJECT_DESTRUCTURING); Node node = newNode(Token.OBJECT_PATTERN); for (ParseTree child : tree.fields) { Node childNode = processObjectPatternElement(child); node.addChildToBack(childNode); } return node; } private Node processObjectPatternElement(ParseTree child) { switch (child.type) { case DEFAULT_PARAMETER: // shorthand with a default value // let { /** inlineType */ name = default } = something; return processObjectPatternShorthandWithDefault(child.asDefaultParameter()); case PROPERTY_NAME_ASSIGNMENT: return processObjectPatternPropertyNameAssignment(child.asPropertyNameAssignment()); case COMPUTED_PROPERTY_DEFINITION: // let {[expression]: /** inlineType */ name} = something; ComputedPropertyDefinitionTree computedPropertyDefinition = child.asComputedPropertyDefinition(); return processObjectPatternComputedPropertyDefinition(computedPropertyDefinition); case OBJECT_REST: // let {...restObject} = someObject; maybeWarnForFeature(child, Feature.OBJECT_PATTERN_REST); Node target = transformNodeWithInlineComments(child.asObjectRest().assignmentTarget); Node rest = newNode(Token.OBJECT_REST, target); setSourceInfo(rest, child); return rest; default: throw new IllegalStateException("Unexpected object pattern element: " + child); } } /** * Process an object pattern element using shorthand and a default value. * *

e.g. the `/** inlineType * / name = default` part of this code * *


     * let { /** inlineType * / name = default } = something;
     * 
*/ private Node processObjectPatternShorthandWithDefault(DefaultParameterTree defaultParameter) { Node defaultValueNode = processDefaultParameter(defaultParameter); // Store in AST as non-shorthand form & just note it was originally shorthand // {name: /**inlineType */ name = default } Node nameNode = defaultValueNode.getFirstChild(); Node stringKeyNode = newStringNode(Token.STRING_KEY, nameNode.getString()); setSourceInfo(stringKeyNode, nameNode); stringKeyNode.setShorthandProperty(true); stringKeyNode.addChildToBack(defaultValueNode); return stringKeyNode; } /** * Processes property name assignments in an object pattern. * *

Covers these cases. * *


     *   let {name} = someObject;
     *   let {key: name} = someObject;
     *   let {key: name = something} = someObject;
     * 
*/ private Node processObjectPatternPropertyNameAssignment( PropertyNameAssignmentTree propertyNameAssignment) { Node key = processObjectLitKeyAsString(propertyNameAssignment.name); key.setToken(Token.STRING_KEY); ParseTree targetTree = propertyNameAssignment.value; final Node valueNode; if (targetTree == null) { // `let { /** inlineType */ key } = something;` // The key is also the target name. valueNode = processNameWithInlineComments(propertyNameAssignment.name.asIdentifier()); key.setShorthandProperty(true); } else { valueNode = processDestructuringElementTarget(targetTree); } key.addChildToFront(valueNode); return key; } private Node processDestructuringElementTarget(ParseTree targetTree) { final Node valueNode; if (targetTree.type == ParseTreeType.DEFAULT_PARAMETER) { // let {key: /** inlineType */ name = default} = something; // let [/** inlineType */ name = default] = something; // processDefaultParameter() knows how to apply the inline JSDoc, if any, to the right node valueNode = processDefaultParameter(targetTree.asDefaultParameter()); } else if (targetTree.type == ParseTreeType.IDENTIFIER_EXPRESSION) { // let {key: /** inlineType */ name} = something // let [/** inlineType */ name] = something // Allow inline JSDoc on the name, since we may well be declaring it here. valueNode = processNameWithInlineComments(targetTree.asIdentifierExpression()); } else { // ({prop: /** string */ ns.a.b} = someObject); // NOTE: CheckJSDoc will report an error for this case, since we want qualified names to be // declared with individual statements, like `/** @type {string} */ ns.a.b;` valueNode = transformNodeWithInlineComments(targetTree); } return valueNode; } /** * Processes a computed property in an object pattern. * *

Covers these cases: * *


     *   let {[expression]: name} = someObject;
     *   let {[expression]: name = defaultValue} = someObject;
     * 
*/ private Node processObjectPatternComputedPropertyDefinition( ComputedPropertyDefinitionTree computedPropertyDefinition) { maybeWarnForFeature(computedPropertyDefinition, Feature.COMPUTED_PROPERTIES); Node expressionNode = transform(computedPropertyDefinition.property); ParseTree valueTree = computedPropertyDefinition.value; Node valueNode = processDestructuringElementTarget(valueTree); Node computedPropertyNode = newNode(Token.COMPUTED_PROP, expressionNode, valueNode); setSourceInfo(computedPropertyNode, computedPropertyDefinition); return computedPropertyNode; } Node processAstRoot(ProgramTree rootNode) { Node scriptNode = newNode(Token.SCRIPT); for (ParseTree child : rootNode.sourceElements) { scriptNode.addChildToBack(transform(child)); } parseDirectives(scriptNode); boolean isGoogModule = isGoogModuleFile(scriptNode); if (isGoogModule || features.has(Feature.MODULES)) { Node moduleNode = newNode(Token.MODULE_BODY); setSourceInfo(moduleNode, rootNode); moduleNode.addChildrenToBack(scriptNode.removeChildren()); scriptNode.addChildToBack(moduleNode); if (isGoogModule) { scriptNode.putBooleanProp(Node.GOOG_MODULE, true); } else { scriptNode.putBooleanProp(Node.ES6_MODULE, true); } } return scriptNode; } private boolean isGoogModuleFile(Node scriptNode) { checkArgument(scriptNode.isScript()); if (!scriptNode.hasChildren()) { return false; } Node exprResult = scriptNode.getFirstChild(); if (!exprResult.isExprResult()) { return false; } Node call = exprResult.getFirstChild(); if (!call.isCall()) { return false; } return call.getFirstChild().matchesQualifiedName("goog.module"); } /** * Parse the directives, encode them in the AST, and remove their nodes. * * For information on ES5 directives, see section 14.1 of * ECMA-262, Edition 5. * * It would be nice if Rhino would eventually take care of this for * us, but right now their directive-processing is a one-off. */ private void parseDirectives(Node node) { // Remove all the directives, and encode them in the AST. ImmutableSet.Builder directives = null; while (isDirective(node.getFirstChild())) { String directive = node.removeFirstChild().getFirstChild().getString(); if (directives == null) { directives = new ImmutableSet.Builder<>(); } directives.add(directive); } if (directives != null) { ImmutableSet result = directives.build(); if (result.size() == 1 && result.contains("use strict")) { // Use a shared set. result = USE_STRICT_ONLY; } node.setDirectives(result); } } private boolean isDirective(Node n) { if (n == null) { return false; } Token nType = n.getToken(); return nType == Token.EXPR_RESULT && n.getFirstChild().isString() && ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString()); } Node processBlock(BlockTree blockNode) { Node node = newNode(Token.BLOCK); for (ParseTree child : blockNode.statements) { node.addChildToBack(transform(child)); } return node; } Node processBreakStatement(BreakStatementTree statementNode) { Node node = newNode(Token.BREAK); if (statementNode.getLabel() != null) { Node labelName = transformLabelName(statementNode.name); node.addChildToBack(labelName); } return node; } Node transformLabelName(IdentifierToken token) { Node label = newStringNode(Token.LABEL_NAME, token.value); setSourceInfo(label, token); return label; } Node processConditionalExpression(ConditionalExpressionTree exprNode) { return newNode( Token.HOOK, transform(exprNode.condition), transform(exprNode.left), transform(exprNode.right)); } Node processContinueStatement(ContinueStatementTree statementNode) { Node node = newNode(Token.CONTINUE); if (statementNode.getLabel() != null) { Node labelName = transformLabelName(statementNode.name); node.addChildToBack(labelName); } return node; } Node processDoLoop(DoWhileStatementTree loopNode) { return newNode( Token.DO, transformBlock(loopNode.body), transform(loopNode.condition)); } Node processElementGet(MemberLookupExpressionTree getNode) { return newNode( Token.GETELEM, transform(getNode.operand), transform(getNode.memberExpression)); } Node processOptChainElementGet(OptionalMemberLookupExpressionTree getNode) { maybeWarnForFeature(getNode, Feature.OPTIONAL_CHAINING); Node getElem = newNode( Token.OPTCHAIN_GETELEM, transform(getNode.operand), transform(getNode.memberExpression)); getElem.setIsOptionalChainStart(getNode.isStartOfOptionalChain); return getElem; } /** * @param exprNode unused */ Node processEmptyStatement(EmptyStatementTree exprNode) { return newNode(Token.EMPTY); } Node processExpressionStatement(ExpressionStatementTree statementNode) { Node node = newNode(Token.EXPR_RESULT); node.addChildToBack(transform(statementNode.expression)); return node; } Node processForInLoop(ForInStatementTree loopNode) { // TODO(bradfordcsmith): Rename initializer to something more intuitive like "lhs" Node initializer = transform(loopNode.initializer); return newNode( Token.FOR_IN, initializer, transform(loopNode.collection), transformBlock(loopNode.body)); } Node processForOf(ForOfStatementTree loopNode) { maybeWarnForFeature(loopNode, Feature.FOR_OF); Node initializer = transform(loopNode.initializer); return newNode( Token.FOR_OF, initializer, transform(loopNode.collection), transformBlock(loopNode.body)); } Node processForAwaitOf(ForAwaitOfStatementTree loopNode) { maybeWarnForFeature(loopNode, Feature.FOR_AWAIT_OF); Node initializer = transform(loopNode.initializer); return newNode( Token.FOR_AWAIT_OF, initializer, transform(loopNode.collection), transformBlock(loopNode.body)); } Node processForLoop(ForStatementTree loopNode) { Node node = newNode( Token.FOR, transformOrEmpty(loopNode.initializer, loopNode), transformOrEmpty(loopNode.condition, loopNode), transformOrEmpty(loopNode.increment, loopNode)); node.addChildToBack(transformBlock(loopNode.body)); return node; } Node transformOrEmpty(ParseTree tree, ParseTree parent) { if (tree == null) { Node n = newNode(Token.EMPTY); setSourceInfo(n, parent); return n; } return transform(tree); } Node transformOrEmpty(IdentifierToken token, ParseTree parent) { if (token == null) { Node n = newNode(Token.EMPTY); setSourceInfo(n, parent); return n; } return processName(token); } Node processFunctionCall(CallExpressionTree callNode) { Node node = newNode(Token.CALL, transform(callNode.operand)); node.setTrailingComma(callNode.arguments.hasTrailingComma); ArgumentListTree argumentsTree = callNode.arguments; // For each arg, represents a location (end SourcePosition) such that all // trailing comments before this location correspond to that arg. List zones = getEndOfArgCommentZones( argumentsTree.arguments, argumentsTree.commaPositions, argumentsTree.location.end); int argCount = 0; for (ParseTree child : callNode.arguments.arguments) { Node childNode = transform(child); node.addChildToBack(childNode); // The non-trailing comments are already attached to `childNode` in `transform(child)` // call. Now we must attach possible trailing comments to `childNode`. attachPossibleTrailingCommentsForArg(childNode, zones.get(argCount)); } return node; } /** * Calculates, for each arg, a location (end SourcePosition) such that all trailing comments * before this location correspond to that arg. Can be used while processing both function calls * (ArgsList) as well as declarations (ParamList). * * @param args list of arguments or formal parameters (ParseTree nodes) * @param commaPositions list of SourcePositions corresponding to commas in the argsList or * formal parameter list * @param argListEndPosition SourcePosition of the end of argsList or paramList */ List getEndOfArgCommentZones( ImmutableList args, ImmutableList commaPositions, SourcePosition argListEndPosition) { ImmutableList.Builder zones = ImmutableList.builder(); int commaCount = 0; for (ParseTree arg : args) { if (args.size() > commaCount + 1) { // there is a next arg after this arg ParseTree nextParam = args.get(commaCount + 1); if (nextParam.location.start.line > arg.location.end.line) { // Next arg is on a new line; all trailing comments on this line belong to this arg // create a source position to represent the end of current line SourcePosition tempSourcePos = new SourcePosition( null, Integer.MAX_VALUE /* offset */, arg.location.end.line, Integer.MAX_VALUE /*col */); zones.add(tempSourcePos); } else { // Next arg is on the same line; trailing comments before the comma belong to this arg SourcePosition commaPosition = commaPositions.get(commaCount); zones.add(commaPosition); } } else { // last arg; trailing comments till the end of argList belong to this arg zones.add(argListEndPosition); } commaCount++; } return zones.build(); } /** * Attaches trailing comments associated with this arg or formal param to it. * * @param paramNode The node to which we're attaching trailing comment * @param endZone The end location until which we fetch pending comments for attachment */ void attachPossibleTrailingCommentsForArg(Node paramNode, SourcePosition endZone) { NonJSDocComment trailingComment = null; if (hasPendingNonJSDocCommentBefore(endZone)) { trailingComment = combineCommentsIntoSingleComment(getNonJSDocCommentsBefore(endZone)); trailingComment.setIsInline(true); NonJSDocComment nonTrailingComment = paramNode.getNonJSDocComment(); if (nonTrailingComment != null) { // This node has both trailing and non-trailing comment nonTrailingComment.appendTrailingCommentToNonTrailing(trailingComment); } else { trailingComment.setIsTrailing(true); paramNode.setNonJSDocComment(trailingComment); } } } Node processOptChainFunctionCall(OptionalCallExpressionTree callNode) { maybeWarnForFeature(callNode, Feature.OPTIONAL_CHAINING); Node node = newNode(Token.OPTCHAIN_CALL, transform(callNode.operand)); node.setTrailingComma(callNode.hasTrailingComma); for (ParseTree child : callNode.arguments.arguments) { node.addChildToBack(transform(child)); } node.setIsOptionalChainStart(callNode.isStartOfOptionalChain); return node; } Node processFunction(FunctionDeclarationTree functionTree) { boolean isDeclaration = (functionTree.kind == FunctionDeclarationTree.Kind.DECLARATION); boolean isMember = (functionTree.kind == FunctionDeclarationTree.Kind.MEMBER); boolean isArrow = (functionTree.kind == FunctionDeclarationTree.Kind.ARROW); boolean isAsync = functionTree.isAsync; boolean isGenerator = functionTree.isGenerator; boolean isSignature = (functionTree.functionBody.type == ParseTreeType.EMPTY_STATEMENT); if (isGenerator) { maybeWarnForFeature(functionTree, Feature.GENERATORS); } if (isMember) { maybeWarnForFeature(functionTree, Feature.MEMBER_DECLARATIONS); } if (isArrow) { maybeWarnForFeature(functionTree, Feature.ARROW_FUNCTIONS); } if (isAsync) { maybeWarnForFeature(functionTree, Feature.ASYNC_FUNCTIONS); } if (isGenerator && isAsync) { maybeWarnForFeature(functionTree, Feature.ASYNC_GENERATORS); } IdentifierToken name = functionTree.name; Node newName; if (name != null) { newName = processNameWithInlineComments(name); } else { if (isDeclaration || isMember) { errorReporter.error( "unnamed function statement", sourceName, lineno(functionTree), charno(functionTree)); // Return the bare minimum to put the AST in a valid state. newName = createMissingNameNode(); } else { newName = newStringNode(Token.NAME, ""); } // Old Rhino tagged the empty name node with the line number of the // declaration. setSourceInfo(newName, functionTree); } Node node = newNode(Token.FUNCTION); if (isMember) { newName.setString(""); } node.addChildToBack(newName); maybeProcessGenerics(node.getFirstChild(), functionTree.generics); node.addChildToBack(transform(functionTree.formalParameterList)); maybeProcessType(node, functionTree.returnType); Node bodyNode = transform(functionTree.functionBody); if (!isArrow && !isSignature && !bodyNode.isBlock()) { // When in "keep going" mode the parser tries to parse some constructs the // compiler doesn't support, repair it here. checkState(config.runMode() == Config.RunMode.KEEP_GOING); bodyNode = IR.block(); } parseDirectives(bodyNode); node.addChildToBack(bodyNode); node.setIsGeneratorFunction(isGenerator); node.setIsArrowFunction(isArrow); node.setIsAsyncFunction(isAsync); node.putBooleanProp(Node.OPT_ES6_TYPED, functionTree.isOptional); Node result; if (isMember) { setSourceInfo(node, functionTree); Node member = newStringNode(Token.MEMBER_FUNCTION_DEF, name.value); member.addChildToBack(node); member.setStaticMember(functionTree.isStatic); maybeProcessAccessibilityModifier(functionTree, member, functionTree.access); node.setDeclaredTypeExpression(node.getDeclaredTypeExpression()); // The source info should only include the identifier, not the entire function expression setSourceInfo(member, name); result = member; } else { result = node; } return result; } Node processFormalParameterList(FormalParameterListTree tree) { Node params = newNode(Token.PARAM_LIST); params.setTrailingComma(tree.hasTrailingComma); if (!checkParameters(tree.parameters)) { return params; } ImmutableList parameters = tree.parameters; List zones = getEndOfArgCommentZones(parameters, tree.commaPositions, tree.location.end); int argCount = 0; for (ParseTree param : tree.parameters) { final Node paramNode; switch (param.type) { case DEFAULT_PARAMETER: // processDefaultParameter() knows how to find and apply inline JSDoc to the right node paramNode = processDefaultParameter(param.asDefaultParameter()); break; case ITER_REST: maybeWarnForFeature(param, Feature.REST_PARAMETERS); paramNode = transformNodeWithInlineComments(param); break; default: paramNode = transformNodeWithInlineComments(param); // Reusing the logic to attach trailing comments used from call-site argsList attachPossibleTrailingCommentsForArg(paramNode, zones.get(argCount)); break; } // Children must be simple names, default parameters, rest // parameters, or destructuring patterns. checkState( paramNode.isName() || paramNode.isRest() || paramNode.isArrayPattern() || paramNode.isObjectPattern() || paramNode.isDefaultValue()); params.addChildToBack(paramNode); argCount++; } return params; } Node processDefaultParameter(DefaultParameterTree tree) { maybeWarnForFeature(tree, Feature.DEFAULT_PARAMETERS); ParseTree targetTree = tree.lhs; Node targetNode; if (targetTree.type == ParseTreeType.IDENTIFIER_EXPRESSION) { // allow inline JSDoc on an identifier // let { /** inlineType */ x = defaultValue } = someObject; // TODO(bradfordcsmith): Do we need to allow inline JSDoc for qualified names, too? targetNode = processNameWithInlineComments(targetTree.asIdentifierExpression()); } else { // ({prop: /** string */ ns.a.b = 'foo'} = someObject); // NOTE: CheckJSDoc will report an error for this case, since we want qualified names to be // declared with individual statements, like `/** @type {string} */ ns.a.b;` targetNode = transformNodeWithInlineComments(targetTree); } final Node defaultValueExpression = transform(tree.defaultValue); Node defaultValueNode = newNode(Token.DEFAULT_VALUE, targetNode, defaultValueExpression); reportErrorIfYieldOrAwaitInDefaultValue(defaultValueNode); setSourceInfo(defaultValueNode, tree); return defaultValueNode; } Node processIterRest(IterRestTree tree) { Node target = transformNodeWithInlineComments(tree.assignmentTarget); return newNode(Token.ITER_REST, target); } Node processIterSpread(IterSpreadTree tree) { maybeWarnForFeature(tree, Feature.SPREAD_EXPRESSIONS); return newNode(Token.ITER_SPREAD, transform(tree.expression)); } Node processObjectSpread(ObjectSpreadTree tree) { maybeWarnForFeature(tree, Feature.OBJECT_LITERALS_WITH_SPREAD); return newNode(Token.OBJECT_SPREAD, transform(tree.expression)); } Node processIfStatement(IfStatementTree statementNode) { Node node = newNode(Token.IF); node.addChildToBack(transform(statementNode.condition)); node.addChildToBack(transformBlock(statementNode.ifClause)); if (statementNode.elseClause != null) { node.addChildToBack(transformBlock(statementNode.elseClause)); } return node; } Node processBinaryExpression(BinaryOperatorTree exprNode) { if (hasPendingCommentBefore(exprNode.right)) { if (exprNode.operator.type == TokenType.STAR_STAR || exprNode.operator.type == TokenType.STAR_STAR_EQUAL) { maybeWarnForFeature(exprNode, Feature.EXPONENT_OP); } else if (exprNode.operator.type == TokenType.QUESTION_QUESTION) { maybeWarnForFeature(exprNode, Feature.NULL_COALESCE_OP); } return newNode( transformBinaryTokenType(exprNode.operator.type), transform(exprNode.left), transform(exprNode.right)); } else { // No JSDoc, we can traverse out of order. return processBinaryExpressionHelper(exprNode); } } // Deep binary ops (typical string concatentations) can cause stack overflows, // avoid recursing in this case and loop instead. private Node processBinaryExpressionHelper(BinaryOperatorTree exprTree) { Node root = null; Node current = null; Node previous = null; while (exprTree != null) { if (exprTree.operator.type == TokenType.STAR_STAR || exprTree.operator.type == TokenType.STAR_STAR_EQUAL) { maybeWarnForFeature(exprTree, Feature.EXPONENT_OP); } if (exprTree.operator.type == TokenType.QUESTION_QUESTION) { maybeWarnForFeature(exprTree, Feature.NULL_COALESCE_OP); } previous = current; // Skip the first child but recurse normally into the right operand as typically this isn't // deep and because we have already checked that there isn't any JSDoc we can traverse // out of order. current = newNode( transformBinaryTokenType(exprTree.operator.type), transform(exprTree.right)); // We have inlined "transform" here. Normally, we would need to handle the JSDoc here but we // know there is no JSDoc to attach, which simplifies things. setSourceInfo(current, exprTree); // As the iteration continues add the left operand. if (previous != null) { previous.addChildToFront(current); } if (exprTree.left instanceof BinaryOperatorTree) { // continue with the left hand child exprTree = (BinaryOperatorTree) exprTree.left; } else { // Finish things off, add the left operand to the current node. Node leftNode = transform(exprTree.left); current.addChildToFront(leftNode); // Nothing left to do. exprTree = null; } // Save the top binary op, this is the result to return. if (root == null) { root = current; } } return root; } /** * @param node unused. */ Node processDebuggerStatement(DebuggerStatementTree node) { return newNode(Token.DEBUGGER); } /** * @param node unused. */ Node processThisExpression(ThisExpressionTree node) { return newNode(Token.THIS); } Node processLabeledStatement(LabelledStatementTree labelTree) { Node statement = transform(labelTree.statement); if (statement.isFunction() || statement.isClass() || statement.isLet() || statement.isConst()) { errorReporter.error( "Lexical declarations are only allowed at top level or inside a block.", sourceName, lineno(labelTree), charno(labelTree)); return statement; // drop the LABEL node so that the resulting AST is valid } return newNode(Token.LABEL, transformLabelName(labelTree.name), statement); } Node processName(IdentifierExpressionTree nameNode) { return processName(nameNode, false); } Node processName(IdentifierExpressionTree nameNode, boolean asString) { return processName(nameNode.identifierToken, asString); } Node processName(IdentifierToken identifierToken) { return processName(identifierToken, false); } Node processName(IdentifierToken identifierToken, boolean asString) { Node node; if (asString) { node = newStringNode(Token.STRING, identifierToken.value); } else { JSDocInfo info = handleJsDoc(identifierToken); maybeWarnReservedKeyword(identifierToken); node = newStringNode(Token.NAME, identifierToken.value); if (info != null) { node.setJSDocInfo(info); } } setSourceInfo(node, identifierToken); return node; } Node processString(LiteralToken token) { checkArgument(token.type == TokenType.STRING); Node node = newStringNode(Token.STRING, normalizeString(token, false)); setSourceInfo(node, token); return node; } Node processTemplateLiteralToken(TemplateLiteralToken token) { checkArgument( token.type == TokenType.NO_SUBSTITUTION_TEMPLATE || token.type == TokenType.TEMPLATE_HEAD || token.type == TokenType.TEMPLATE_MIDDLE || token.type == TokenType.TEMPLATE_TAIL); Node node; if (token.hasError()) { node = newTemplateLitStringNode(null, token.value); } else { node = newTemplateLitStringNode(normalizeString(token, true), token.value); } setSourceInfo(node, token); return node; } private Node processNameWithInlineComments(IdentifierExpressionTree identifierExpression) { return processNameWithInlineComments(identifierExpression.identifierToken); } Node processNameWithInlineComments(IdentifierToken identifierToken) { JSDocInfo info = handleInlineJsDoc(identifierToken); NonJSDocComment associatedNonJSDocComment = null; if (config.jsDocParsingMode() == JsDocParsing.INCLUDE_ALL_COMMENTS) { ArrayList nonJSDocComments = getNonJSDocComments(identifierToken); if (!nonJSDocComments.isEmpty()) { associatedNonJSDocComment = combineCommentsIntoSingleComment(nonJSDocComments); } } maybeWarnReservedKeyword(identifierToken); Node node = newStringNode(Token.NAME, identifierToken.value); if (info != null) { node.setJSDocInfo(info); } if (config.jsDocParsingMode() == JsDocParsing.INCLUDE_ALL_COMMENTS) { if (associatedNonJSDocComment != null) { node.setNonJSDocComment(associatedNonJSDocComment); } } setSourceInfo(node, identifierToken); return node; } private void maybeWarnKeywordProperty(Node node) { if (TokenStream.isKeyword(node.getString())) { features = features.with(Feature.KEYWORDS_AS_PROPERTIES); if (config.languageMode() == LanguageMode.ECMASCRIPT3) { errorReporter.warning(INVALID_ES3_PROP_NAME, sourceName, node.getLineno(), node.getCharno()); } } } private void maybeWarnReservedKeyword(IdentifierToken token) { String identifier = token.value; boolean isIdentifier = false; if (TokenStream.isKeyword(identifier)) { features = features.with(Feature.ES3_KEYWORDS_AS_IDENTIFIERS); isIdentifier = config.languageMode() == LanguageMode.ECMASCRIPT3; } if (reservedKeywords != null && reservedKeywords.contains(identifier)) { features = features.with(Feature.KEYWORDS_AS_PROPERTIES); isIdentifier = config.languageMode() == LanguageMode.ECMASCRIPT3; } if (isIdentifier) { errorReporter.error( "identifier is a reserved word", sourceName, lineno(token.location.start), charno(token.location.start)); } } Node processNewExpression(NewExpressionTree exprNode) { Node node = newNode( Token.NEW, transform(exprNode.operand)); node.setTrailingComma(exprNode.hasTrailingComma); if (exprNode.arguments != null) { for (ParseTree arg : exprNode.arguments.arguments) { node.addChildToBack(transform(arg)); } } return node; } Node processNumberLiteral(LiteralExpressionTree literalNode) { double value = normalizeNumber(literalNode.literalToken.asLiteral()); Node number = newNumberNode(value); setSourceInfo(number, literalNode); return number; } Node processBigIntLiteral(LiteralExpressionTree literalNode) { maybeWarnForFeature(literalNode, Feature.BIGINT); BigInteger value = normalizeBigInt(literalNode.literalToken.asLiteral()); Node bigint = newBigIntNode(value); setSourceInfo(bigint, literalNode); return bigint; } Node processObjectLiteral(ObjectLiteralExpressionTree objTree) { Node node = newNode(Token.OBJECTLIT); node.setTrailingComma(objTree.hasTrailingComma); boolean maybeWarn = false; for (ParseTree el : objTree.propertyNameAndValues) { if (el.type == ParseTreeType.DEFAULT_PARAMETER) { // (e.g. var o = { x=4 };) This is only parsed for compatibility with object patterns. errorReporter.error( "Default value cannot appear at top level of an object literal.", sourceName, lineno(el), 0); continue; } else if (el.type == ParseTreeType.GET_ACCESSOR && maybeReportGetter(el)) { continue; } else if (el.type == ParseTreeType.SET_ACCESSOR && maybeReportSetter(el)) { continue; } Node key = transform(el); if (!key.isComputedProp() && !key.isQuotedString() && !key.isSpread() && !currentFileIsExterns) { maybeWarnKeywordProperty(key); } if (key.isShorthandProperty()) { maybeWarn = true; } node.addChildToBack(key); } if (maybeWarn) { maybeWarnForFeature(objTree, Feature.EXTENDED_OBJECT_LITERALS); } return node; } Node processComputedPropertyDefinition(ComputedPropertyDefinitionTree tree) { maybeWarnForFeature(tree, Feature.COMPUTED_PROPERTIES); return newNode(Token.COMPUTED_PROP, transform(tree.property), transform(tree.value)); } Node processComputedPropertyMemberVariable(ComputedPropertyMemberVariableTree tree) { maybeWarnForFeature(tree, Feature.COMPUTED_PROPERTIES); maybeWarnTypeSyntax(tree, Feature.MEMBER_VARIABLE_IN_CLASS); Node n = newNode(Token.COMPUTED_PROP, transform(tree.property)); maybeProcessType(n, tree.declaredType); n.putBooleanProp(Node.COMPUTED_PROP_VARIABLE, true); n.putProp(Node.ACCESS_MODIFIER, tree.access); n.setStaticMember(tree.isStatic); maybeProcessAccessibilityModifier(tree, n, tree.access); return n; } Node processComputedPropertyMethod(ComputedPropertyMethodTree tree) { maybeWarnForFeature(tree, Feature.COMPUTED_PROPERTIES); Node n = newNode(Token.COMPUTED_PROP, transform(tree.property), transform(tree.method)); n.putBooleanProp(Node.COMPUTED_PROP_METHOD, true); if (tree.method.asFunctionDeclaration().isStatic) { n.setStaticMember(true); } maybeProcessAccessibilityModifier(tree, n, tree.access); return n; } Node processComputedPropertyGetter(ComputedPropertyGetterTree tree) { maybeWarnForFeature(tree, Feature.COMPUTED_PROPERTIES); Node key = transform(tree.property); Node body = transform(tree.body); Node function = IR.function(IR.name(""), IR.paramList(), body); function.useSourceInfoIfMissingFromForTree(body); Node n = newNode(Token.COMPUTED_PROP, key, function); n.putBooleanProp(Node.COMPUTED_PROP_GETTER, true); n.putBooleanProp(Node.STATIC_MEMBER, tree.isStatic); return n; } Node processComputedPropertySetter(ComputedPropertySetterTree tree) { maybeWarnForFeature(tree, Feature.COMPUTED_PROPERTIES); Node key = transform(tree.property); Node paramList = processFormalParameterList(tree.parameter); setSourceInfo(paramList, tree.parameter); Node body = transform(tree.body); Node function = IR.function(IR.name(""), paramList, body); function.useSourceInfoIfMissingFromForTree(body); Node n = newNode(Token.COMPUTED_PROP, key, function); n.putBooleanProp(Node.COMPUTED_PROP_SETTER, true); n.putBooleanProp(Node.STATIC_MEMBER, tree.isStatic); return n; } Node processGetAccessor(GetAccessorTree tree) { Node key = processObjectLitKeyAsString(tree.propertyName); key.setToken(Token.GETTER_DEF); Node body = transform(tree.body); Node dummyName = newStringNode(Token.NAME, ""); setSourceInfo(dummyName, tree.body); Node paramList = newNode(Token.PARAM_LIST); setSourceInfo(paramList, tree.body); Node value = newNode(Token.FUNCTION, dummyName, paramList, body); setSourceInfo(value, tree.body); key.addChildToFront(value); maybeProcessType(value, tree.returnType); key.setStaticMember(tree.isStatic); return key; } Node processSetAccessor(SetAccessorTree tree) { Node key = processObjectLitKeyAsString(tree.propertyName); key.setToken(Token.SETTER_DEF); Node paramList = processFormalParameterList(tree.parameter); setSourceInfo(paramList, tree.parameter); Node body = transform(tree.body); Node dummyName = newStringNode(Token.NAME, ""); setSourceInfo(dummyName, tree.propertyName); Node value = newNode(Token.FUNCTION, dummyName, paramList, body); setSourceInfo(value, tree.body); key.addChildToFront(value); key.setStaticMember(tree.isStatic); return key; } Node processPropertyNameAssignment(PropertyNameAssignmentTree tree) { Node key = processObjectLitKeyAsString(tree.name); key.setToken(Token.STRING_KEY); if (tree.value != null) { key.addChildToFront(transform(tree.value)); } else { Node value = key.cloneNode(); key.setShorthandProperty(true); value.setToken(Token.NAME); key.addChildToFront(value); } return key; } private void checkParenthesizedExpression(ParenExpressionTree exprNode) { if (exprNode.expression.type == ParseTreeType.COMMA_EXPRESSION) { List commaNodes = exprNode.expression.asCommaExpression().expressions; ParseTree lastChild = Iterables.getLast(commaNodes); if (lastChild.type == ParseTreeType.ITER_REST) { errorReporter.error( "A rest parameter must be in a parameter list.", sourceName, lineno(lastChild), charno(lastChild)); } } } Node processParenthesizedExpression(ParenExpressionTree exprNode) { checkParenthesizedExpression(exprNode); Node expr = transform(exprNode.expression); expr.setIsParenthesized(true); return expr; } Node processPropertyGet(MemberExpressionTree getNode) { Node leftChild = transform(getNode.operand); IdentifierToken nodeProp = getNode.memberName; Node rightChild = processObjectLitKeyAsString(nodeProp); if (!rightChild.isQuotedString() && !currentFileIsExterns) { maybeWarnKeywordProperty(rightChild); } return newNode(Token.GETPROP, leftChild, rightChild); } Node processOptChainPropertyGet(OptionalMemberExpressionTree getNode) { maybeWarnForFeature(getNode, Feature.OPTIONAL_CHAINING); Node leftChild = transform(getNode.operand); IdentifierToken nodeProp = getNode.memberName; Node rightChild = processObjectLitKeyAsString(nodeProp); if (!rightChild.isQuotedString() && !currentFileIsExterns) { maybeWarnKeywordProperty(rightChild); } Node getProp = newNode(Token.OPTCHAIN_GETPROP, leftChild, rightChild); getProp.setIsOptionalChainStart(getNode.isStartOfOptionalChain); return getProp; } Node processRegExpLiteral(LiteralExpressionTree literalTree) { LiteralToken token = literalTree.literalToken.asLiteral(); Node literalStringNode = newStringNode(normalizeRegex(token)); // TODO(johnlenz): fix the source location. setSourceInfo(literalStringNode, token); Node node = newNode(Token.REGEXP, literalStringNode); String rawRegex = token.value; int lastSlash = rawRegex.lastIndexOf('/'); String flags = ""; if (lastSlash < rawRegex.length()) { flags = rawRegex.substring(lastSlash + 1); } validateRegExpFlags(literalTree, flags); if (!flags.isEmpty()) { Node flagsNode = newStringNode(flags); // TODO(johnlenz): fix the source location. setSourceInfo(flagsNode, token); node.addChildToBack(flagsNode); } return node; } private void validateRegExpFlags(LiteralExpressionTree tree, String flags) { for (char flag : Lists.charactersOf(flags)) { switch (flag) { case 'g': case 'i': case 'm': break; case 'u': case 'y': Feature feature = flag == 'u' ? Feature.REGEXP_FLAG_U : Feature.REGEXP_FLAG_Y; maybeWarnForFeature(tree, feature); break; case 's': maybeWarnForFeature(tree, Feature.REGEXP_FLAG_S); break; default: errorReporter.error( "Invalid RegExp flag '" + flag + "'", sourceName, lineno(tree), charno(tree)); } } } Node processReturnStatement(ReturnStatementTree statementNode) { Node node = newNode(Token.RETURN); if (statementNode.expression != null) { node.addChildToBack(transform(statementNode.expression)); } return node; } Node processStringLiteral(LiteralExpressionTree literalTree) { LiteralToken token = literalTree.literalToken.asLiteral(); Node n = processString(token); String value = n.getString(); if (value.indexOf('\u000B') != -1) { // NOTE(nicksantos): In JavaScript, there are 3 ways to // represent a vertical tab: \v, \x0B, \u000B. // The \v notation was added later, and is not understood // on IE. So we need to preserve it as-is. This is really // obnoxious, because we do not have a good way to represent // how the original string was encoded without making the // representation of strings much more complicated. // // To handle this, we look at the original source test, and // mark the string as \v-encoded or not. If a string is // \v encoded, then all the vertical tabs in that string // will be encoded with a \v. int start = token.location.start.offset; int end = token.location.end.offset; if (start < sourceString.length() && (sourceString.substring(start, min(sourceString.length(), end)).contains("\\v"))) { n.putBooleanProp(Node.SLASH_V, true); } } return n; } Node processTemplateLiteral(TemplateLiteralExpressionTree tree) { maybeWarnForFeature(tree, Feature.TEMPLATE_LITERALS); Node templateLitNode = newNode(Token.TEMPLATELIT); setSourceInfo(templateLitNode, tree); Node node = tree.operand == null ? templateLitNode : newNode(Token.TAGGED_TEMPLATELIT, transform(tree.operand), templateLitNode); for (ParseTree child : tree.elements) { templateLitNode.addChildToBack(transform(child)); } return node; } Node processTemplateLiteralPortion(TemplateLiteralPortionTree tree) { return processTemplateLiteralToken(tree.value.asTemplateLiteral()); } Node processTemplateSubstitution(TemplateSubstitutionTree tree) { return newNode(Token.TEMPLATELIT_SUB, transform(tree.expression)); } Node processSwitchCase(CaseClauseTree caseNode) { ParseTree expr = caseNode.expression; Node node = newNode(Token.CASE, transform(expr)); Node block = newNode(Token.BLOCK); block.setIsAddedBlock(true); setSourceInfo(block, caseNode); if (caseNode.statements != null) { for (ParseTree child : caseNode.statements) { block.addChildToBack(transform(child)); } } node.addChildToBack(block); return node; } Node processSwitchDefault(DefaultClauseTree caseNode) { Node node = newNode(Token.DEFAULT_CASE); Node block = newNode(Token.BLOCK); block.setIsAddedBlock(true); setSourceInfo(block, caseNode); if (caseNode.statements != null) { for (ParseTree child : caseNode.statements) { block.addChildToBack(transform(child)); } } node.addChildToBack(block); return node; } Node processSwitchStatement(SwitchStatementTree statementNode) { Node node = newNode(Token.SWITCH, transform(statementNode.expression)); for (ParseTree child : statementNode.caseClauses) { node.addChildToBack(transform(child)); } return node; } Node processThrowStatement(ThrowStatementTree statementNode) { return newNode(Token.THROW, transform(statementNode.value)); } Node processTryStatement(TryStatementTree statementNode) { Node node = newNode(Token.TRY, transformBlock(statementNode.body)); Node block = newNode(Token.BLOCK); boolean lineSet = false; ParseTree cc = statementNode.catchBlock; if (cc != null) { // Mark the enclosing block at the same line as the first catch // clause. setSourceInfo(block, cc); lineSet = true; block.addChildToBack(transform(cc)); } node.addChildToBack(block); ParseTree finallyBlock = statementNode.finallyBlock; if (finallyBlock != null) { node.addChildToBack(transformBlock(finallyBlock)); } // If we didn't set the line on the catch clause, then // we've got an empty catch clause. Set its line to be the same // as the finally block (to match Old Rhino's behavior.) if (!lineSet && (finallyBlock != null)) { setSourceInfo(block, finallyBlock); } return node; } Node processCatchClause(CatchTree clauseNode) { if (clauseNode.exception.type == ParseTreeType.EMPTY_STATEMENT) { maybeWarnForFeature(clauseNode, Feature.OPTIONAL_CATCH_BINDING); } return newNode(Token.CATCH, transform(clauseNode.exception), transformBlock(clauseNode.catchBody)); } Node processFinally(FinallyTree finallyNode) { return transformBlock(finallyNode.block); } Node processUnaryExpression(UnaryExpressionTree exprNode) { Token type = transformUnaryTokenType(exprNode.operator.type); Node operand = transform(exprNode.operand); if (type == Token.NEG && (operand.isNumber() || operand.isBigInt())) { if (operand.isBigInt()) { operand.setBigInt(operand.getBigInt().negate()); } else { operand.setDouble(-operand.getDouble()); } operand.setLineno(-1); setSourceInfo(operand, exprNode.operator.getStart(), exprNode.operand.getEnd()); return operand; } else { if (type == Token.DELPROP && !(operand.isGetProp() || operand.isGetElem() || operand.isName() || operand.isOptChainGetProp() || operand.isOptChainGetElem())) { String msg = "Invalid delete operand. Only properties can be deleted."; errorReporter.error( msg, sourceName, operand.getLineno(), 0); } if (type == Token.POS && operand.isBigInt()) { errorReporter.error( "Cannot convert a BigInt value to a number", sourceName, operand.getLineno(), 0); } return newNode(type, operand); } } Node processUpdateExpression(UpdateExpressionTree updateExpr) { Token type = transformUpdateTokenType(updateExpr.operator.type); Node operand = transform(updateExpr.operand); return createUpdateNode( type, updateExpr.operatorPosition == OperatorPosition.POSTFIX, operand); } private Node createUpdateNode(Token type, boolean postfix, Node operand) { Node assignTarget = operand.isCast() ? operand.getFirstChild() : operand; if (!assignTarget.isValidAssignmentTarget()) { errorReporter.error( SimpleFormat.format("Invalid %s %s operand.", (postfix ? "postfix" : "prefix"), (type == Token.INC ? "increment" : "decrement")), sourceName, operand.getLineno(), operand.getCharno()); } Node node = newNode(type, operand); node.putBooleanProp(Node.INCRDECR_PROP, postfix); return node; } Node processVariableStatement(VariableStatementTree stmt) { // TODO(moz): Figure out why we still need the special handling return transformDispatcher.process(stmt.declarations); } Node processVariableDeclarationList(VariableDeclarationListTree decl) { Token declType; switch (decl.declarationType) { case CONST: maybeWarnForFeature(decl, Feature.CONST_DECLARATIONS); declType = Token.CONST; break; case LET: maybeWarnForFeature(decl, Feature.LET_DECLARATIONS); declType = Token.LET; break; case VAR: declType = Token.VAR; break; default: throw new IllegalStateException(); } Node node = newNode(declType); for (VariableDeclarationTree child : decl.declarations) { node.addChildToBack(transformNodeWithInlineComments(child)); } return node; } Node processVariableDeclaration(VariableDeclarationTree decl) { Node node = transformNodeWithInlineComments(decl.lvalue); Node lhs = node.isDestructuringPattern() ? newNode(Token.DESTRUCTURING_LHS, node) : node; if (decl.initializer != null) { Node initializer = transform(decl.initializer); lhs.addChildToBack(initializer); setLength(lhs, decl.location.start, decl.location.end); } maybeProcessType(lhs, decl.declaredType); return lhs; } Node processWhileLoop(WhileStatementTree stmt) { return newNode( Token.WHILE, transform(stmt.condition), transformBlock(stmt.body)); } Node processWithStatement(WithStatementTree stmt) { return newNode( Token.WITH, transform(stmt.expression), transformBlock(stmt.body)); } /** * @param tree unused */ Node processMissingExpression(MissingPrimaryExpressionTree tree) { // This will already have been reported as an error by the parser. // Try to create something valid that ide mode might be able to // continue with. return createMissingExpressionNode(); } private Node createMissingNameNode() { return newStringNode(Token.NAME, "__missing_name__"); } private Node createMissingExpressionNode() { return newStringNode(Token.NAME, "__missing_expression__"); } Node processIllegalToken(ParseTree node) { errorReporter.error( "Unsupported syntax: " + node.type, sourceName, lineno(node), 0); return newNode(Token.EMPTY); } /** Reports an illegal getter and returns true if the language mode is too low. */ boolean maybeReportGetter(ParseTree node) { features = features.with(Feature.GETTER); if (config.languageMode() == LanguageMode.ECMASCRIPT3) { errorReporter.error( GETTER_ERROR_MESSAGE, sourceName, lineno(node), 0); return true; } return false; } /** Reports an illegal setter and returns true if the language mode is too low. */ boolean maybeReportSetter(ParseTree node) { features = features.with(Feature.SETTER); if (config.languageMode() == LanguageMode.ECMASCRIPT3) { errorReporter.error( SETTER_ERROR_MESSAGE, sourceName, lineno(node), 0); return true; } return false; } Node processBooleanLiteral(LiteralExpressionTree literal) { return newNode(transformBooleanTokenType( literal.literalToken.type)); } /** * @param literal unused */ Node processNullLiteral(LiteralExpressionTree literal) { return newNode(Token.NULL); } /** * @param literal unused */ Node processNull(NullTree literal) { // NOTE: This is not a NULL literal but a placeholder node such as in // an array with "holes". return newNode(Token.EMPTY); } Node processCommaExpression(CommaExpressionTree tree) { Node root = newNode(Token.COMMA); SourcePosition start = tree.expressions.get(0).location.start; SourcePosition end = tree.expressions.get(1).location.end; setSourceInfo(root, start, end); for (ParseTree expr : tree.expressions) { int count = root.getChildCount(); if (count < 2) { root.addChildToBack(transform(expr)); } else { end = expr.location.end; root = newNode(Token.COMMA, root, transform(expr)); setSourceInfo(root, start, end); } } return root; } Node processClassDeclaration(ClassDeclarationTree tree) { maybeWarnForFeature(tree, Feature.CLASSES); Node name = transformOrEmpty(tree.name, tree); maybeProcessGenerics(name, tree.generics); Node superClass = transformOrEmpty(tree.superClass, tree); if (!superClass.isEmpty()) { features = features.with(Feature.CLASS_EXTENDS); } Node interfaces = transformListOrEmpty(Token.IMPLEMENTS, tree.interfaces); Node body = newNode(Token.CLASS_MEMBERS); setSourceInfo(body, tree); boolean hasConstructor = false; for (ParseTree child : tree.elements) { switch (child.type) { case MEMBER_VARIABLE: case COMPUTED_PROPERTY_MEMBER_VARIABLE: maybeWarnTypeSyntax(child, Feature.MEMBER_VARIABLE_IN_CLASS); break; default: break; } switch (child.type) { case COMPUTED_PROPERTY_GETTER: case COMPUTED_PROPERTY_SETTER: case GET_ACCESSOR: case SET_ACCESSOR: features = features.with(Feature.CLASS_GETTER_SETTER); break; default: break; } boolean childIsCtor = validateClassConstructorMember(child); // Has side-effects. if (childIsCtor) { if (hasConstructor) { errorReporter.error( "Class may have only one constructor.", // sourceName, lineno(child), charno(child)); } hasConstructor = true; } body.addChildToBack(transform(child)); } Node classNode = newNode(Token.CLASS, name, superClass, body); if (!interfaces.isEmpty()) { maybeWarnTypeSyntax(tree, Feature.IMPLEMENTS); classNode.putProp(Node.IMPLEMENTS, interfaces); } return classNode; } /** Returns {@code true} iff this member is a legal class constructor. */ private boolean validateClassConstructorMember(ParseTree member) { final com.google.javascript.jscomp.parsing.parser.Token memberName; final boolean isStatic; final boolean hasIllegalModifier; switch (member.type) { case GET_ACCESSOR: GetAccessorTree getter = member.asGetAccessor(); memberName = getter.propertyName; isStatic = getter.isStatic; hasIllegalModifier = true; break; case SET_ACCESSOR: SetAccessorTree setter = member.asSetAccessor(); memberName = setter.propertyName; isStatic = setter.isStatic; hasIllegalModifier = true; break; case FUNCTION_DECLARATION: FunctionDeclarationTree method = member.asFunctionDeclaration(); memberName = method.name; isStatic = method.isStatic; hasIllegalModifier = method.isGenerator || method.isAsync; break; default: // Computed properties aren't an issue here because they aren't used as the class // constructor, regardless of their name. return false; } if (isStatic) { // Statics are fine because they're never the class constructor. return false; } if (!memberName.type.equals(TokenType.IDENTIFIER) || !memberName.asIdentifier().value.equals("constructor")) { // There's only a potential issue if the member is named "constructor". // TODO(b/123769080): Also check for quoted string literals with the value "constructor". return false; } if (hasIllegalModifier) { errorReporter.error( "Class constructor may not be getter, setter, async, or generator.", sourceName, lineno(member), charno(member)); return false; } return true; } Node processInterfaceDeclaration(InterfaceDeclarationTree tree) { maybeWarnTypeSyntax(tree, Feature.INTERFACE); Node name = processName(tree.name); maybeProcessGenerics(name, tree.generics); Node superInterfaces = transformListOrEmpty(Token.INTERFACE_EXTENDS, tree.superInterfaces); Node body = newNode(Token.INTERFACE_MEMBERS); setSourceInfo(body, tree); for (ParseTree child : tree.elements) { body.addChildToBack(transform(child)); } return newNode(Token.INTERFACE, name, superInterfaces, body); } Node processEnumDeclaration(EnumDeclarationTree tree) { maybeWarnTypeSyntax(tree, Feature.ENUM); Node name = processName(tree.name); Node body = newNode(Token.ENUM_MEMBERS); setSourceInfo(body, tree); for (ParseTree child : tree.members) { Node childNode = transform(child); // NOTE: we may need to "undo" the shorthand property normalization, // since this syntax has a different meaning in enums. if (childNode.isShorthandProperty()) { childNode.removeChild(childNode.getLastChild()); childNode.setShorthandProperty(false); } body.addChildToBack(childNode); } return newNode(Token.ENUM, name, body); } Node processSuper(SuperExpressionTree tree) { maybeWarnForFeature(tree, Feature.SUPER); return newNode(Token.SUPER); } Node processNewTarget(NewTargetExpressionTree tree) { maybeWarnForFeature(tree, Feature.NEW_TARGET); return newNode(Token.NEW_TARGET); } Node processMemberVariable(MemberVariableTree tree) { Node member = newStringNode(Token.MEMBER_VARIABLE_DEF, tree.name.value); maybeProcessType(member, tree.declaredType); member.setStaticMember(tree.isStatic); member.putBooleanProp(Node.OPT_ES6_TYPED, tree.isOptional); maybeProcessAccessibilityModifier(tree, member, tree.access); return member; } Node processYield(YieldExpressionTree tree) { Node yield = newNode(Token.YIELD); if (tree.expression != null) { yield.addChildToBack(transform(tree.expression)); } yield.setYieldAll(tree.isYieldAll); return yield; } Node processAwait(AwaitExpressionTree tree) { maybeWarnForFeature(tree, Feature.ASYNC_FUNCTIONS); Node await = newNode(Token.AWAIT); await.addChildToBack(transform(tree.expression)); return await; } Node processExportDecl(ExportDeclarationTree tree) { maybeWarnForFeature(tree, Feature.MODULES); Node decls = null; if (tree.isExportAll) { checkState(tree.declaration == null && tree.exportSpecifierList == null); } else if (tree.declaration != null) { checkState(tree.exportSpecifierList == null); decls = transform(tree.declaration); } else { decls = transformList(Token.EXPORT_SPECS, tree.exportSpecifierList); } if (decls == null) { decls = newNode(Token.EMPTY); } setSourceInfo(decls, tree); Node export = newNode(Token.EXPORT, decls); if (tree.from != null) { Node from = processString(tree.from); export.addChildToBack(from); } export.putBooleanProp(Node.EXPORT_ALL_FROM, tree.isExportAll); export.putBooleanProp(Node.EXPORT_DEFAULT, tree.isDefault); return export; } Node processExportSpec(ExportSpecifierTree tree) { Node importedName = processName(tree.importedName, true); importedName.setToken(Token.NAME); Node exportSpec = newNode(Token.EXPORT_SPEC, importedName); if (tree.destinationName == null) { exportSpec.setShorthandProperty(true); exportSpec.addChildToBack(importedName.cloneTree()); } else { Node destinationName = processName(tree.destinationName, true); destinationName.setToken(Token.NAME); exportSpec.addChildToBack(destinationName); } return exportSpec; } Node processImportDecl(ImportDeclarationTree tree) { maybeWarnForFeature(tree, Feature.MODULES); Node firstChild = transformOrEmpty(tree.defaultBindingIdentifier, tree); Node secondChild; if (tree.nameSpaceImportIdentifier == null) { secondChild = transformListOrEmpty(Token.IMPORT_SPECS, tree.importSpecifierList); // Currently source info is "import {foo} from '...';" expression. If needed this should be // changed to use only "{foo}" part. setSourceInfo(secondChild, tree); } else { secondChild = newStringNode(Token.IMPORT_STAR, tree.nameSpaceImportIdentifier.value); setSourceInfo(secondChild, tree.nameSpaceImportIdentifier); } Node thirdChild = processString(tree.moduleSpecifier); return newNode(Token.IMPORT, firstChild, secondChild, thirdChild); } Node processImportSpec(ImportSpecifierTree tree) { Node importedName = processName(tree.importedName, true); importedName.setToken(Token.NAME); Node importSpec = newNode(Token.IMPORT_SPEC, importedName); if (tree.destinationName == null) { importSpec.setShorthandProperty(true); importSpec.addChildToBack(importedName.cloneTree()); } else { importSpec.addChildToBack(processName(tree.destinationName)); } return importSpec; } Node processDynamicImport(DynamicImportTree dynamicImportNode) { maybeWarnForFeature(dynamicImportNode, Feature.DYNAMIC_IMPORT); Node argument = transform(dynamicImportNode.argument); return newNode(Token.DYNAMIC_IMPORT, argument); } Node processImportMeta(ImportMetaExpressionTree tree) { maybeWarnForFeature(tree, Feature.MODULES); maybeWarnForFeature(tree, Feature.IMPORT_META); return newNode(Token.IMPORT_META); } Node processTypeName(TypeNameTree tree) { Node typeNode; if (tree.segments.size() == 1) { String typeName = tree.segments.get(0); switch (typeName) { case "any": typeNode = cloneProps(anyType()); break; case "number": typeNode = cloneProps(numberType()); break; case "boolean": typeNode = cloneProps(booleanType()); break; case "string": typeNode = cloneProps(stringType()); break; case "void": typeNode = cloneProps(voidType()); break; case "undefined": typeNode = cloneProps(undefinedType()); break; default: typeNode = cloneProps(namedType(tree.segments)); break; } } else { typeNode = cloneProps(namedType(tree.segments)); } setSourceInfo(typeNode, tree); return typeNode; } Node processTypedParameter(TypedParameterTree typeAnnotation) { Node param = transform(typeAnnotation.param); maybeProcessType(param, typeAnnotation.typeAnnotation); return param; } Node processOptionalParameter(OptionalParameterTree optionalParam) { maybeWarnTypeSyntax(optionalParam, Feature.OPTIONAL_PARAMETER); Node param = transform(optionalParam.param); param.putBooleanProp(Node.OPT_ES6_TYPED, true); return param; } private void maybeProcessType(Node typeTarget, ParseTree typeTree) { if (typeTree != null) { recordJsDoc(typeTree.location, typeTarget.getJSDocInfo()); Node typeExpression = convertTypeTree(typeTree); if (typeExpression.isString()) { typeExpression = cloneProps( new TypeDeclarationNode(Token.STRING, typeExpression.getString())); } typeTarget.setDeclaredTypeExpression((TypeDeclarationNode) typeExpression); } } private void maybeProcessGenerics(Node n, GenericTypeListTree generics) { if (generics != null) { maybeWarnTypeSyntax(generics, Feature.GENERICS); n.putProp(Node.GENERIC_TYPE_LIST, transform(generics)); } } private Node convertTypeTree(ParseTree typeTree) { maybeWarnTypeSyntax(typeTree, Feature.TYPE_ANNOTATION); return transform(typeTree); } Node processParameterizedType(ParameterizedTypeTree tree) { ImmutableList.Builder arguments = ImmutableList.builder(); for (ParseTree arg : tree.typeArguments) { arguments.add((TypeDeclarationNode) transform(arg)); } TypeDeclarationNode typeName = (TypeDeclarationNode) transform(tree.typeName); return cloneProps(parameterizedType(typeName, arguments.build())); } Node processArrayType(ArrayTypeTree tree) { return cloneProps(arrayType(transform(tree.elementType))); } Node processRecordType(RecordTypeTree tree) { TypeDeclarationNode node = new TypeDeclarationNode(Token.RECORD_TYPE); for (ParseTree child : tree.members) { node.addChildToBack(transform(child)); } return cloneProps(node); } Node processUnionType(UnionTypeTree tree) { ImmutableList.Builder options = ImmutableList.builder(); for (ParseTree option : tree.types) { options.add((TypeDeclarationNode) transform(option)); } return cloneProps(unionType(options.build())); } Node processTypeAlias(TypeAliasTree tree) { maybeWarnTypeSyntax(tree, Feature.TYPE_ALIAS); Node typeAlias = newStringNode(Token.TYPE_ALIAS, tree.alias.value); typeAlias.addChildToFront(transform(tree.original)); return typeAlias; } Node processAmbientDeclaration(AmbientDeclarationTree tree) { maybeWarnTypeSyntax(tree, Feature.AMBIENT_DECLARATION); return newNode(Token.DECLARE, transform(tree.declaration)); } Node processNamespaceDeclaration(NamespaceDeclarationTree tree) { maybeWarnTypeSyntax(tree, Feature.NAMESPACE_DECLARATION); Node name = processNamespaceName(tree.name); Node body = newNode(Token.NAMESPACE_ELEMENTS); setSourceInfo(body, tree); for (ParseTree child : tree.elements) { body.addChildToBack(transform(child)); } return newNode(Token.NAMESPACE, name, body); } Node processNamespaceName(NamespaceNameTree name) { ImmutableList segments = name.segments; if (segments.size() == 1) { Node namespaceName = newStringNode(Token.NAME, segments.get(0)); setSourceInfo(namespaceName, name); return namespaceName; } else { Iterator segmentsIt = segments.iterator(); Node node = IR.name(segmentsIt.next()); setSourceInfo(node, name); while (segmentsIt.hasNext()) { Node string = newStringNode(Token.STRING, segmentsIt.next()); setSourceInfo(string, name); node = newNode(Token.GETPROP, node, string); setSourceInfo(node, name); } return node; } } Node processIndexSignature(IndexSignatureTree tree) { maybeWarnTypeSyntax(tree, Feature.INDEX_SIGNATURE); Node name = transform(tree.name); Node indexType = name.getDeclaredTypeExpression(); if (indexType.getToken() != Token.NUMBER_TYPE && indexType.getToken() != Token.STRING_TYPE) { errorReporter.error( "Index signature parameter type must be 'string' or 'number'", sourceName, lineno(tree.name), charno(tree.name)); } Node signature = newNode(Token.INDEX_SIGNATURE, name); maybeProcessType(signature, tree.declaredType); return signature; } Node processCallSignature(CallSignatureTree tree) { maybeWarnTypeSyntax( tree, tree.isNew ? Feature.CONSTRUCTOR_SIGNATURE : Feature.CALL_SIGNATURE); Node signature = newNode(Token.CALL_SIGNATURE, transform(tree.formalParameterList)); maybeProcessType(signature, tree.returnType); maybeProcessGenerics(signature, tree.generics); signature.putBooleanProp(Node.CONSTRUCT_SIGNATURE, tree.isNew); return signature; } private boolean checkParameters(ImmutableList params) { boolean seenOptional = false; boolean good = true; for (int i = 0; i < params.size(); i++) { ParseTree param = params.get(i); Node type = null; if (param.type == ParseTreeType.TYPED_PARAMETER) { TypedParameterTree typedParam = param.asTypedParameter(); type = transform(typedParam.typeAnnotation); param = typedParam.param; } switch (param.type) { case IDENTIFIER_EXPRESSION: if (seenOptional) { errorReporter.error( "A required parameter cannot follow an optional parameter.", sourceName, lineno(param), charno(param)); good = false; } break; case OPTIONAL_PARAMETER: seenOptional = true; break; case ITER_REST: if (i != params.size() - 1) { errorReporter.error( "A rest parameter must be last in a parameter list.", sourceName, lineno(param), charno(param)); good = false; } if (type != null && type.getToken() != Token.ARRAY_TYPE) { errorReporter.error( "A rest parameter must be of an array type.", sourceName, lineno(param), charno(param)); good = false; } break; default: } } return good; } Node processFunctionType(FunctionTypeTree tree) { LinkedHashMap requiredParams = new LinkedHashMap<>(); LinkedHashMap optionalParams = new LinkedHashMap<>(); String restName = null; TypeDeclarationNode restType = null; if (checkParameters(tree.formalParameterList.parameters)) { for (ParseTree param : tree.formalParameterList.parameters) { TypeDeclarationNode type = null; if (param.type == ParseTreeType.TYPED_PARAMETER) { TypedParameterTree typedParam = param.asTypedParameter(); type = (TypeDeclarationNode) transform(typedParam.typeAnnotation); param = typedParam.param; } switch (param.type) { case IDENTIFIER_EXPRESSION: requiredParams.put( param.asIdentifierExpression().identifierToken.value, type); break; case OPTIONAL_PARAMETER: maybeWarnTypeSyntax(param, Feature.OPTIONAL_PARAMETER); optionalParams.put( param.asOptionalParameter().param.asIdentifierExpression() .identifierToken.value, type); break; case ITER_REST: // TypeScript doesn't allow destructuring parameters, so the assignment target must // be an identifier. restName = param .asIterRest() .assignmentTarget .asIdentifierExpression() .identifierToken .value; restType = type; break; default: throw new IllegalStateException("Illegal parameter type: " + param.type); } } } return cloneProps(functionType(transform(tree.returnType), requiredParams, optionalParams, restName, restType)); } Node processTypeQuery(TypeQueryTree tree) { Iterator segmentsIt = tree.segments.iterator(); Node node = newStringNode(Token.NAME, segmentsIt.next()); while (segmentsIt.hasNext()) { node = IR.getprop(node, IR.string(segmentsIt.next())); } return cloneProps(new TypeDeclarationNode(Token.TYPEOF, node)); } Node processGenericTypeList(GenericTypeListTree tree) { Node list = newNode(Token.GENERIC_TYPE_LIST); for (Map.Entry generic : tree.generics.entrySet()) { Node type = newStringNode(Token.GENERIC_TYPE, generic.getKey().value); ParseTree bound = generic.getValue(); if (bound != null) { type.addChildToBack(transform(bound)); } list.addChildToBack(type); } return list; } private Node transformList( Token type, ImmutableList list) { Node n = newNode(type); for (ParseTree tree : list) { n.addChildToBack(transform(tree)); } return n; } private Node transformListOrEmpty( Token type, ImmutableList list) { if (list == null || list.isEmpty()) { return newNode(Token.EMPTY); } else { return transformList(type, list); } } void maybeProcessAccessibilityModifier(ParseTree parseTree, Node n, @Nullable TokenType type) { if (type != null) { Visibility access; switch (type) { case PUBLIC: access = Visibility.PUBLIC; break; case PROTECTED: access = Visibility.PROTECTED; break; case PRIVATE: access = Visibility.PRIVATE; break; default: throw new IllegalStateException("Unexpected access modifier type"); } maybeWarnTypeSyntax(parseTree, Feature.ACCESSIBILITY_MODIFIER); n.putProp(Node.ACCESS_MODIFIER, access); } } void maybeWarnTypeSyntax(ParseTree node, Feature feature) { if (config.languageMode() != LanguageMode.TYPESCRIPT) { errorReporter.warning( "type syntax is only supported in ES6 typed mode: " + feature, sourceName, lineno(node), charno(node)); } features = features.with(feature); recordTypeSyntax(node.location); } Node unsupportedLanguageFeature(ParseTree node, String feature) { errorReporter.error( "unsupported language feature: " + feature, sourceName, lineno(node), charno(node)); return createMissingExpressionNode(); } Node processLiteralExpression(LiteralExpressionTree expr) { switch (expr.literalToken.type) { case NUMBER: return processNumberLiteral(expr); case STRING: return processStringLiteral(expr); case BIGINT: return processBigIntLiteral(expr); case FALSE: case TRUE: return processBooleanLiteral(expr); case NULL: return processNullLiteral(expr); case REGULAR_EXPRESSION: return processRegExpLiteral(expr); default: throw new IllegalStateException("Unexpected literal type: " + expr.literalToken.getClass() + " type: " + expr.literalToken.type); } } public Node process(ParseTree node) { switch (node.type) { case BINARY_OPERATOR: return processBinaryExpression(node.asBinaryOperator()); case ARRAY_LITERAL_EXPRESSION: return processArrayLiteral(node.asArrayLiteralExpression()); case TEMPLATE_LITERAL_EXPRESSION: return processTemplateLiteral(node.asTemplateLiteralExpression()); case TEMPLATE_LITERAL_PORTION: return processTemplateLiteralPortion(node.asTemplateLiteralPortion()); case TEMPLATE_SUBSTITUTION: return processTemplateSubstitution(node.asTemplateSubstitution()); case UNARY_EXPRESSION: return processUnaryExpression(node.asUnaryExpression()); case BLOCK: return processBlock(node.asBlock()); case BREAK_STATEMENT: return processBreakStatement(node.asBreakStatement()); case CALL_EXPRESSION: return processFunctionCall(node.asCallExpression()); case OPT_CHAIN__CALL_EXPRESSION: return processOptChainFunctionCall(node.asOptionalCallExpression()); case CASE_CLAUSE: return processSwitchCase(node.asCaseClause()); case DEFAULT_CLAUSE: return processSwitchDefault(node.asDefaultClause()); case CATCH: return processCatchClause(node.asCatch()); case CONTINUE_STATEMENT: return processContinueStatement(node.asContinueStatement()); case DO_WHILE_STATEMENT: return processDoLoop(node.asDoWhileStatement()); case EMPTY_STATEMENT: return processEmptyStatement(node.asEmptyStatement()); case EXPRESSION_STATEMENT: return processExpressionStatement(node.asExpressionStatement()); case DEBUGGER_STATEMENT: return processDebuggerStatement(node.asDebuggerStatement()); case THIS_EXPRESSION: return processThisExpression(node.asThisExpression()); case FOR_STATEMENT: return processForLoop(node.asForStatement()); case FOR_IN_STATEMENT: return processForInLoop(node.asForInStatement()); case FUNCTION_DECLARATION: return processFunction(node.asFunctionDeclaration()); case MEMBER_LOOKUP_EXPRESSION: return processElementGet(node.asMemberLookupExpression()); case OPT_CHAIN_MEMBER_LOOKUP_EXPRESSION: return processOptChainElementGet(node.asOptionalMemberLookupExpression()); case MEMBER_EXPRESSION: return processPropertyGet(node.asMemberExpression()); case OPT_CHAIN_MEMBER_EXPRESSION: return processOptChainPropertyGet(node.asOptionalMemberExpression()); case CONDITIONAL_EXPRESSION: return processConditionalExpression(node.asConditionalExpression()); case IF_STATEMENT: return processIfStatement(node.asIfStatement()); case LABELLED_STATEMENT: return processLabeledStatement(node.asLabelledStatement()); case PAREN_EXPRESSION: return processParenthesizedExpression(node.asParenExpression()); case IDENTIFIER_EXPRESSION: return processName(node.asIdentifierExpression()); case NEW_EXPRESSION: return processNewExpression(node.asNewExpression()); case OBJECT_LITERAL_EXPRESSION: return processObjectLiteral(node.asObjectLiteralExpression()); case COMPUTED_PROPERTY_DEFINITION: return processComputedPropertyDefinition(node.asComputedPropertyDefinition()); case COMPUTED_PROPERTY_GETTER: return processComputedPropertyGetter(node.asComputedPropertyGetter()); case COMPUTED_PROPERTY_MEMBER_VARIABLE: return processComputedPropertyMemberVariable(node.asComputedPropertyMemberVariable()); case COMPUTED_PROPERTY_METHOD: return processComputedPropertyMethod(node.asComputedPropertyMethod()); case COMPUTED_PROPERTY_SETTER: return processComputedPropertySetter(node.asComputedPropertySetter()); case RETURN_STATEMENT: return processReturnStatement(node.asReturnStatement()); case UPDATE_EXPRESSION: return processUpdateExpression(node.asUpdateExpression()); case PROGRAM: return processAstRoot(node.asProgram()); case LITERAL_EXPRESSION: // STRING, NUMBER, TRUE, FALSE, NULL, REGEXP return processLiteralExpression(node.asLiteralExpression()); case SWITCH_STATEMENT: return processSwitchStatement(node.asSwitchStatement()); case THROW_STATEMENT: return processThrowStatement(node.asThrowStatement()); case TRY_STATEMENT: return processTryStatement(node.asTryStatement()); case VARIABLE_STATEMENT: // var const let return processVariableStatement(node.asVariableStatement()); case VARIABLE_DECLARATION_LIST: return processVariableDeclarationList(node.asVariableDeclarationList()); case VARIABLE_DECLARATION: return processVariableDeclaration(node.asVariableDeclaration()); case WHILE_STATEMENT: return processWhileLoop(node.asWhileStatement()); case WITH_STATEMENT: return processWithStatement(node.asWithStatement()); case COMMA_EXPRESSION: return processCommaExpression(node.asCommaExpression()); case NULL: // this is not the null literal return processNull(node.asNull()); case FINALLY: return processFinally(node.asFinally()); case MISSING_PRIMARY_EXPRESSION: return processMissingExpression(node.asMissingPrimaryExpression()); case PROPERTY_NAME_ASSIGNMENT: return processPropertyNameAssignment(node.asPropertyNameAssignment()); case GET_ACCESSOR: return processGetAccessor(node.asGetAccessor()); case SET_ACCESSOR: return processSetAccessor(node.asSetAccessor()); case FORMAL_PARAMETER_LIST: return processFormalParameterList(node.asFormalParameterList()); case CLASS_DECLARATION: return processClassDeclaration(node.asClassDeclaration()); case SUPER_EXPRESSION: return processSuper(node.asSuperExpression()); case NEW_TARGET_EXPRESSION: return processNewTarget(node.asNewTargetExpression()); case YIELD_EXPRESSION: return processYield(node.asYieldStatement()); case AWAIT_EXPRESSION: return processAwait(node.asAwaitExpression()); case FOR_OF_STATEMENT: return processForOf(node.asForOfStatement()); case FOR_AWAIT_OF_STATEMENT: return processForAwaitOf(node.asForAwaitOfStatement()); case EXPORT_DECLARATION: return processExportDecl(node.asExportDeclaration()); case EXPORT_SPECIFIER: return processExportSpec(node.asExportSpecifier()); case IMPORT_DECLARATION: return processImportDecl(node.asImportDeclaration()); case IMPORT_SPECIFIER: return processImportSpec(node.asImportSpecifier()); case DYNAMIC_IMPORT_EXPRESSION: return processDynamicImport(node.asDynamicImportExpression()); case IMPORT_META_EXPRESSION: return processImportMeta(node.asImportMetaExpression()); case ARRAY_PATTERN: return processArrayPattern(node.asArrayPattern()); case OBJECT_PATTERN: return processObjectPattern(node.asObjectPattern()); case COMPREHENSION: return processComprehension(node.asComprehension()); case COMPREHENSION_FOR: return processComprehensionFor(node.asComprehensionFor()); case COMPREHENSION_IF: return processComprehensionIf(node.asComprehensionIf()); case DEFAULT_PARAMETER: return processDefaultParameter(node.asDefaultParameter()); case ITER_REST: return processIterRest(node.asIterRest()); case ITER_SPREAD: return processIterSpread(node.asIterSpread()); // ES2019 case OBJECT_REST: return processObjectPatternElement(node.asObjectRest()); case OBJECT_SPREAD: return processObjectSpread(node.asObjectSpread()); // ES6 Typed case TYPE_NAME: return processTypeName(node.asTypeName()); case TYPED_PARAMETER: return processTypedParameter(node.asTypedParameter()); case OPTIONAL_PARAMETER: return processOptionalParameter(node.asOptionalParameter()); case PARAMETERIZED_TYPE_TREE: return processParameterizedType(node.asParameterizedType()); case ARRAY_TYPE: return processArrayType(node.asArrayType()); case RECORD_TYPE: return processRecordType(node.asRecordType()); case UNION_TYPE: return processUnionType(node.asUnionType()); case FUNCTION_TYPE: return processFunctionType(node.asFunctionType()); case TYPE_QUERY: return processTypeQuery(node.asTypeQuery()); case GENERIC_TYPE_LIST: return processGenericTypeList(node.asGenericTypeList()); case MEMBER_VARIABLE: return processMemberVariable(node.asMemberVariable()); case INTERFACE_DECLARATION: return processInterfaceDeclaration(node.asInterfaceDeclaration()); case ENUM_DECLARATION: return processEnumDeclaration(node.asEnumDeclaration()); case TYPE_ALIAS: return processTypeAlias(node.asTypeAlias()); case AMBIENT_DECLARATION: return processAmbientDeclaration(node.asAmbientDeclaration()); case NAMESPACE_DECLARATION: return processNamespaceDeclaration(node.asNamespaceDeclaration()); case INDEX_SIGNATURE: return processIndexSignature(node.asIndexSignature()); case CALL_SIGNATURE: return processCallSignature(node.asCallSignature()); // TODO(johnlenz): handle these or remove parser support case ARGUMENT_LIST: default: break; } return processIllegalToken(node); } } private void reportErrorIfYieldOrAwaitInDefaultValue(Node defaultValueNode) { Node yieldNode = findNodeTypeInExpression(defaultValueNode, Token.YIELD); if (yieldNode != null) { errorReporter.error( "`yield` is illegal in parameter default value.", yieldNode.getSourceFileName(), yieldNode.getLineno(), yieldNode.getCharno()); } Node awaitNode = findNodeTypeInExpression(defaultValueNode, Token.AWAIT); if (awaitNode != null) { errorReporter.error( "`await` is illegal in parameter default value.", awaitNode.getSourceFileName(), awaitNode.getLineno(), awaitNode.getCharno()); } } /** * Tries to find a node with the given token in the given expression. Returns the first one found * in pre-order traversal or `null` if none found. Will not traverse into function or class * expressions. */ private static Node findNodeTypeInExpression(Node expressionNode, Token token) { Deque worklist = new ArrayDeque<>(); worklist.add(expressionNode); while (!worklist.isEmpty()) { Node node = worklist.remove(); if (node.getToken() == token) { return node; } else if (!node.isFunction() && !node.isClass()) { for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { worklist.add(child); } } } return null; } String normalizeRegex(LiteralToken token) { String value = token.value; final int lastSlash = value.lastIndexOf('/'); int cur = value.indexOf('\\'); if (cur == -1) { return value.substring(1, lastSlash); } StringBuilder result = new StringBuilder(); int start = 1; while (cur != -1) { result.append(value, start, cur); cur++; // skip the escape char. char c = value.charAt(cur); switch (c) { // Characters for which the backslash is semantically important. case '^': case '$': case '\\': case '/': case '.': case '*': case '+': case '?': case '(': case ')': case '[': case ']': case '{': case '}': case '|': case '-': case 'b': case 'B': case 'c': case 'd': case 'D': case 'f': case 'n': case 'p': // 2018 unicode property escapes case 'P': // 2018 unicode property escapes case 'r': case 's': case 'S': case 't': case 'u': case 'v': case 'w': case 'W': case 'x': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': result.append('\\'); // fallthrough default: // For all other characters, the backslash has no effect, so just append the next char. result.append(c); } start = cur + 1; cur = value.indexOf('\\', start); } result.append(value, start, lastSlash); return result.toString(); } String normalizeString(LiteralToken token, boolean templateLiteral) { String value = token.value; // and are normalized as . For raw template literal string values: this is the // spec behaviour. For regular string literals: they can only be part of a line continuation, // which we want to scrub. value = value.replaceAll("\r\n?", "\n"); int start = templateLiteral ? 0 : 1; // skip the leading quote int cur = value.indexOf('\\'); if (cur == -1) { // short circuit no escapes. return templateLiteral ? value : value.substring(1, value.length() - 1); } StringBuilder result = new StringBuilder(); while (cur != -1) { result.append(value, start, cur); cur += 1; // skip the escape char. char c = value.charAt(cur); switch (c) { case 'b': result.append('\b'); break; case 'f': result.append('\f'); break; case 'n': result.append('\n'); break; case 'r': result.append('\r'); break; case 't': result.append('\t'); break; case 'v': result.append('\u000B'); break; case '\n': // line continuation, skip the line break maybeWarnForFeature(token, Feature.STRING_CONTINUATION); errorReporter.warning( STRING_CONTINUATION_WARNING, sourceName, lineno(token.location.start), charno(token.location.start)); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': int numDigits; if (cur + 1 < value.length() && isOctalDigit(value.charAt(cur + 1))) { if (cur + 2 < value.length() && isOctalDigit(value.charAt(cur + 2))) { numDigits = 3; } else { numDigits = 2; } } else { numDigits = 1; } if (inStrictContext() || templateLiteral) { if (c == '0' && numDigits == 1) { // No warning: "\0" followed by a character which is not an octal digit // is allowed in strict mode. } else { errorReporter.warning(OCTAL_STRING_LITERAL_WARNING, sourceName, lineno(token.location.start), charno(token.location.start)); } } result.append((char) parseInt(value.substring(cur, cur + numDigits), 8)); cur += numDigits - 1; break; case 'x': result.append((char) ( hexdigit(value.charAt(cur + 1)) * 0x10 + hexdigit(value.charAt(cur + 2)))); cur += 2; break; case 'u': int escapeEnd; String hexDigits; if (value.charAt(cur + 1) != '{') { // Simple escape with exactly four hex digits: \\uXXXX escapeEnd = cur + 5; hexDigits = value.substring(cur + 1, escapeEnd); } else { // Escape with braces can have any number of hex digits: \\u{XXXXXXX} escapeEnd = cur + 2; while (Character.digit(value.charAt(escapeEnd), 0x10) >= 0) { escapeEnd++; } hexDigits = value.substring(cur + 2, escapeEnd); escapeEnd++; } int codePointValue = parseInt(hexDigits, 0x10); if (codePointValue > 0x10ffff) { errorReporter.error( "Undefined Unicode code-point", sourceName, lineno(token.location.start), charno(token.location.start)); // Compilation should stop, but we should finish the string and find more errors. // These appends are just to have a placeholder for the errored normalization. result.append("\\u{"); result.append(hexDigits); result.append("}"); } else { result.append(Character.toChars(codePointValue)); } cur = escapeEnd - 1; break; case '\'': case '"': case '\\': default: result.append(c); break; } start = cur + 1; cur = value.indexOf('\\', start); } // skip the trailing quote. result.append(value, start, templateLiteral ? value.length() : value.length() - 1); return result.toString(); } boolean isSupportedForInputLanguageMode(Feature feature) { return config.languageMode().featureSet.has(feature); } boolean isEs5OrBetterMode() { return config.languageMode().featureSet.contains(FeatureSet.ES5); } private boolean inStrictContext() { // TODO(johnlenz): in ECMASCRIPT5/6 is a "mixed" mode and we should track the context // that we are in, if we want to support it. return config.strictMode().isStrict(); } double normalizeNumber(LiteralToken token) { String value = token.value; if (value.contains("_")) { value = removeNumericSeparators(value, token); } SourceRange location = token.location; int length = value.length(); checkState(length > 0); checkState(value.charAt(0) != '-' && value.charAt(0) != '+'); if (value.charAt(0) == '.') { return Double.parseDouble('0' + value); } else if (value.charAt(0) == '0' && length > 1) { switch (value.charAt(1)) { case '.': case 'e': case 'E': return Double.parseDouble(value); case 'b': case 'B': { maybeWarnForFeature(token, Feature.BINARY_LITERALS); double v = 0; int c = 1; while (++c < length) { v = (v * 2) + binarydigit(value.charAt(c)); } return v; } case 'o': case 'O': { maybeWarnForFeature(token, Feature.OCTAL_LITERALS); double v = 0; int c = 1; while (++c < length) { v = (v * 8) + octaldigit(value.charAt(c)); } return v; } case 'x': case 'X': { double v = 0; int c = 1; while (++c < length) { v = (v * 0x10) + hexdigit(value.charAt(c)); } return v; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': double v = 0; int c = 0; while (++c < length) { char digit = value.charAt(c); if (isOctalDigit(digit)) { v = (v * 8) + octaldigit(digit); } else { errorReporter.error(INVALID_OCTAL_DIGIT, sourceName, lineno(location.start), charno(location.start)); return 0; } } if (inStrictContext()) { errorReporter.error(INVALID_ES5_STRICT_OCTAL, sourceName, lineno(location.start), charno(location.start)); } else { errorReporter.warning(INVALID_ES5_STRICT_OCTAL, sourceName, lineno(location.start), charno(location.start)); } return v; case '8': case '9': errorReporter.error(INVALID_OCTAL_DIGIT, sourceName, lineno(location.start), charno(location.start)); return 0; default: throw new IllegalStateException( "Unexpected character in number literal: " + value.charAt(1)); } } else { return Double.parseDouble(value); } } BigInteger normalizeBigInt(LiteralToken token) { String value = token.value; value = value.substring(0, value.indexOf('n')); if (value.contains("_")) { value = removeNumericSeparators(value, token); } int length = value.length(); checkState(length > 0); checkState(value.charAt(0) != '-' && value.charAt(0) != '+'); if (value.charAt(0) == '0' && length > 1) { switch (value.charAt(1)) { case 'b': case 'B': maybeWarnForFeature(token, Feature.BINARY_LITERALS); return new BigInteger(value.substring(2), 2); case 'o': case 'O': maybeWarnForFeature(token, Feature.OCTAL_LITERALS); return new BigInteger(value.substring(2), 8); case 'x': case 'X': return new BigInteger(value.substring(2), 16); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': throw new IllegalStateException("Nonzero BigInts can't have a leading zero"); default: throw new IllegalStateException( "Unexpected character in bigint literal: " + value.charAt(1)); } } else { return new BigInteger(value); } } private String removeNumericSeparators(String value, LiteralToken token) { maybeWarnForFeature(token, Feature.NUMERIC_SEPARATOR); return value.replace("_", ""); } private static int binarydigit(char c) { if (c >= '0' && c <= '1') { return (c - '0'); } throw new IllegalStateException("unexpected: " + c); } private static boolean isOctalDigit(char c) { return c >= '0' && c <= '7'; } private static int octaldigit(char c) { if (isOctalDigit(c)) { return (c - '0'); } throw new IllegalStateException("unexpected: " + c); } private static int hexdigit(char c) { switch (c) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: throw new IllegalStateException("unexpected: " + c); } } static Token transformBooleanTokenType(TokenType token) { switch (token) { case TRUE: return Token.TRUE; case FALSE: return Token.FALSE; default: throw new IllegalStateException(String.valueOf(token)); } } static Token transformUpdateTokenType(TokenType token) { switch (token) { case PLUS_PLUS: return Token.INC; case MINUS_MINUS: return Token.DEC; default: throw new IllegalStateException(String.valueOf(token)); } } static Token transformUnaryTokenType(TokenType token) { switch (token) { case BANG: return Token.NOT; case TILDE: return Token.BITNOT; case PLUS: return Token.POS; case MINUS: return Token.NEG; case DELETE: return Token.DELPROP; case TYPEOF: return Token.TYPEOF; case VOID: return Token.VOID; default: throw new IllegalStateException(String.valueOf(token)); } } static Token transformBinaryTokenType(TokenType token) { switch (token) { case BAR: return Token.BITOR; case CARET: return Token.BITXOR; case AMPERSAND: return Token.BITAND; case EQUAL_EQUAL: return Token.EQ; case NOT_EQUAL: return Token.NE; case OPEN_ANGLE: return Token.LT; case LESS_EQUAL: return Token.LE; case CLOSE_ANGLE: return Token.GT; case GREATER_EQUAL: return Token.GE; case LEFT_SHIFT: return Token.LSH; case RIGHT_SHIFT: return Token.RSH; case UNSIGNED_RIGHT_SHIFT: return Token.URSH; case PLUS: return Token.ADD; case MINUS: return Token.SUB; case STAR: return Token.MUL; case SLASH: return Token.DIV; case PERCENT: return Token.MOD; case STAR_STAR: return Token.EXPONENT; case EQUAL_EQUAL_EQUAL: return Token.SHEQ; case NOT_EQUAL_EQUAL: return Token.SHNE; case IN: return Token.IN; case INSTANCEOF: return Token.INSTANCEOF; case COMMA: return Token.COMMA; case EQUAL: return Token.ASSIGN; case BAR_EQUAL: return Token.ASSIGN_BITOR; case CARET_EQUAL: return Token.ASSIGN_BITXOR; case AMPERSAND_EQUAL: return Token.ASSIGN_BITAND; case LEFT_SHIFT_EQUAL: return Token.ASSIGN_LSH; case RIGHT_SHIFT_EQUAL: return Token.ASSIGN_RSH; case UNSIGNED_RIGHT_SHIFT_EQUAL: return Token.ASSIGN_URSH; case PLUS_EQUAL: return Token.ASSIGN_ADD; case MINUS_EQUAL: return Token.ASSIGN_SUB; case STAR_EQUAL: return Token.ASSIGN_MUL; case STAR_STAR_EQUAL: return Token.ASSIGN_EXPONENT; case SLASH_EQUAL: return Token.ASSIGN_DIV; case PERCENT_EQUAL: return Token.ASSIGN_MOD; case OR: return Token.OR; case AND: return Token.AND; case QUESTION_QUESTION: return Token.COALESCE; default: throw new IllegalStateException(String.valueOf(token)); } } // Simple helper to create nodes and set the initial node properties. Node newNode(Token type) { return new Node(type).clonePropsFrom(templateNode); } Node newNode(Token type, Node child1) { return new Node(type, child1).clonePropsFrom(templateNode); } Node newNode(Token type, Node child1, Node child2) { return new Node(type, child1, child2).clonePropsFrom(templateNode); } Node newNode(Token type, Node child1, Node child2, Node child3) { return new Node(type, child1, child2, child3).clonePropsFrom(templateNode); } Node newStringNode(String value) { return IR.string(value).clonePropsFrom(templateNode); } Node newStringNode(Token type, String value) { return Node.newString(type, value).clonePropsFrom(templateNode); } Node newTemplateLitStringNode(String cooked, String raw) { return Node.newTemplateLitString(cooked, raw).clonePropsFrom(templateNode); } Node newNumberNode(Double value) { return IR.number(value).clonePropsFrom(templateNode); } Node newBigIntNode(BigInteger value) { return Node.newBigInt(value).clonePropsFrom(templateNode); } /** * Clone the properties from the template node recursively, skips nodes that * have properties already. */ Node cloneProps(Node n) { if (!n.hasProps()) { n.clonePropsFrom(templateNode); } for (Node child : n.children()) { cloneProps(child); } return n; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy