com.google.javascript.jscomp.parsing.IRFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler Show documentation
Show all versions of closure-compiler Show documentation
Closure Compiler is a JavaScript optimizing compiler. It parses your
JavaScript, analyzes it, removes dead code and rewrites and minimizes
what's left. It also checks syntax, variable references, and types, and
warns about common JavaScript pitfalls. It is used in many of Google's
JavaScript apps, including Gmail, Google Web Search, Google Maps, and
Google Docs.
/*
* 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 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;
}
}
NonJSDocComment nonJSDocComment =
new NonJSDocComment(completeCommentBegin, completeCommentEnd, result);
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);
}
}
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));
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, Math.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;
}
}