com.google.javascript.jscomp.parsing.parser.Parser Maven / Gradle / Ivy
/*
* Copyright 2011 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.parser;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.annotations.FormatString;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
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.ObjectRestTree;
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.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.ErrorReporter;
import com.google.javascript.jscomp.parsing.parser.util.LookaheadErrorReporter;
import com.google.javascript.jscomp.parsing.parser.util.LookaheadErrorReporter.ParseException;
import com.google.javascript.jscomp.parsing.parser.util.SourcePosition;
import com.google.javascript.jscomp.parsing.parser.util.SourceRange;
import java.util.ArrayDeque;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import javax.annotation.Nullable;
/**
* Parses a javascript file.
*
* The various parseX() methods never return null - even when parse errors are encountered.
* Typically parseX() will return a XTree ParseTree. Each ParseTree that is created includes its
* source location. The typical pattern for a parseX() method is:
*
*
* XTree parseX() {
* SourcePosition start = getTreeStartLocation();
* parse X grammar element and its children
* return new XTree(getTreeLocation(start), children);
* }
*
*
* parseX() methods must consume at least 1 token - even in error cases. This prevents infinite
* loops in the parser.
*
*
Many parseX() methods are matched by a 'boolean peekX()' method which will return true if
* the beginning of an X appears at the current location. There are also peek() methods which
* examine the next token. peek() methods must not consume any tokens.
*
*
The eat() method consumes a token and reports an error if the consumed token is not of the
* expected type. The eatOpt() methods consume the next token iff the next token is of the expected
* type and return the consumed token or null if no token was consumed.
*
*
When parse errors are encountered, an error should be reported and the parse should return a
* best guess at the current parse tree.
*
*
When parsing lists, the preferred pattern is:
*
* eat(LIST_START);
* ImmutableList.Builder<ParseTree> elements = ImmutableList.builder();
* while (peekListElement()) {
* elements.add(parseListElement());
* }
* eat(LIST_END);
*
*/
public class Parser {
/**
* Indicates the type of function currently being parsed.
*/
private enum FunctionFlavor {
NORMAL(false, false),
GENERATOR(true, false),
ASYNCHRONOUS(false, true),
ASYNCHRONOUS_GENERATOR(true, true);
final boolean isGenerator;
final boolean isAsynchronous;
FunctionFlavor(boolean isGenerator, boolean isAsynchronous) {
this.isGenerator = isGenerator;
this.isAsynchronous = isAsynchronous;
}
}
private final Scanner scanner;
private final ErrorReporter errorReporter;
private final Config config;
private final CommentRecorder commentRecorder = new CommentRecorder();
private final ArrayDeque functionContextStack = new ArrayDeque<>();
private FeatureSet features = FeatureSet.BARE_MINIMUM;
private SourcePosition lastSourcePosition;
@Nullable private String sourceMapURL;
public Parser(
Config config,
ErrorReporter errorReporter,
SourceFile source,
int offset,
boolean initialGeneratorContext) {
this.config = config;
this.errorReporter = errorReporter;
this.scanner =
new Scanner(config.parseTypeSyntax, errorReporter, commentRecorder, source, offset);
this.functionContextStack.addLast(
initialGeneratorContext ? FunctionFlavor.GENERATOR : FunctionFlavor.NORMAL);
lastSourcePosition = scanner.getPosition();
}
public Parser(Config config, ErrorReporter errorReporter, SourceFile source, int offset) {
this(config, errorReporter, source, offset, false);
}
public Parser(Config config, ErrorReporter errorReporter, SourceFile source) {
this(config, errorReporter, source, 0);
}
public static class Config {
public static enum Mode {
ES3,
ES5,
ES6_OR_ES7,
ES8_OR_GREATER,
TYPESCRIPT,
}
/**
* Indicates that the parser should look for TypeScript-like data type syntax.
*/
// TODO(bradfordcsmith): Make sure all of the type syntax handling code is avoided when
// this is false.
private final boolean parseTypeSyntax;
private final boolean atLeast6;
private final boolean atLeast8;
private final boolean isStrictMode;
private final boolean warnTrailingCommas;
public Config() {
this(Mode.ES8_OR_GREATER, /* isStrictMode */ false);
}
public Config(Mode mode, boolean isStrictMode) {
parseTypeSyntax = mode == Mode.TYPESCRIPT;
atLeast6 = !(mode == Mode.ES3 || mode == Mode.ES5);
atLeast8 = mode == Mode.ES8_OR_GREATER || mode == Mode.TYPESCRIPT;
this.isStrictMode = isStrictMode;
// Generally, we allow everything that is valid in any mode
// we only warn about things that are not represented in the AST.
this.warnTrailingCommas = mode == Mode.ES3;
}
}
private static final String SOURCE_MAPPING_URL_PREFIX = "//# sourceMappingURL=";
private class CommentRecorder implements Scanner.CommentRecorder {
private final ImmutableList.Builder comments = ImmutableList.builder();
private SourcePosition lastCommentEndPosition;
@Override
public void recordComment(Comment.Type type, SourceRange range, String value) {
// If we rewind the token stream, the scanner might pass comments that we've already seen.
// Only record comments past the furthest comment end position we've seen.
// NB: this assumes the CommentRecorder is used for at most one source file.
if (lastCommentEndPosition == null || range.end.offset > this.lastCommentEndPosition.offset) {
value = value.trim();
if (value.startsWith(SOURCE_MAPPING_URL_PREFIX)) {
sourceMapURL = value.substring(SOURCE_MAPPING_URL_PREFIX.length());
}
comments.add(new Comment(value, range, type));
this.lastCommentEndPosition = range.end;
}
}
private ImmutableList getComments() {
return comments.build();
}
}
public List getComments() {
return commentRecorder.getComments();
}
public FeatureSet getFeatures() {
return features;
}
/** Returns the url provided by the sourceMappingURL if any was found. */
@Nullable
public String getSourceMapURL() {
return sourceMapURL;
}
/** Returns true if the string value should be treated as a keyword in the current context. */
private boolean isKeyword(String value) {
return Keywords.isKeyword(value, config.parseTypeSyntax);
}
// 14 Program
public ProgramTree parseProgram() {
try {
SourcePosition start = getTreeStartLocation();
ImmutableList sourceElements = parseGlobalSourceElements();
eat(TokenType.END_OF_FILE);
return new ProgramTree(
getTreeLocation(start), sourceElements, commentRecorder.getComments());
} catch (StackOverflowError e) {
reportError("Too deep recursion while parsing");
return null;
}
}
private ImmutableList parseGlobalSourceElements() {
ImmutableList.Builder result = ImmutableList.builder();
while (!peek(TokenType.END_OF_FILE)) {
result.add(parseScriptElement());
}
return result.build();
}
private ImmutableList parseNamespaceElements() {
ImmutableList.Builder result = ImmutableList.builder();
while (!peek(TokenType.CLOSE_CURLY) && !peek(TokenType.END_OF_FILE)) {
result.add(parseScriptElement());
}
return result.build();
}
private ImmutableList parseAmbientNamespaceElements() {
ImmutableList.Builder result = ImmutableList.builder();
while (peekAmbientNamespaceElement()) {
result.add(parseAmbientNamespaceElement());
}
return result.build();
}
// ImportDeclaration
// ExportDeclaration
// TypeScript InterfaceDeclaration
// TypeScript EnumDeclaration
// TypeScript TypeAlias
// TypeScript AmbientDeclaration
// SourceElement
private ParseTree parseScriptElement() {
if (peekImportDeclaration()) {
return parseImportDeclaration();
}
if (peekExportDeclaration()) {
return parseExportDeclaration(false);
}
if (peekInterfaceDeclaration()) {
return parseInterfaceDeclaration();
}
if (peekEnumDeclaration()) {
return parseEnumDeclaration();
}
if (peekTypeAlias()) {
return parseTypeAlias();
}
if (peekAmbientDeclaration()) {
return parseAmbientDeclaration();
}
if (peekNamespaceDeclaration()) {
return parseNamespaceDeclaration(false);
}
return parseSourceElement();
}
private ParseTree parseAmbientNamespaceElement() {
if (peekInterfaceDeclaration()) {
return parseInterfaceDeclaration();
}
if (peekExportDeclaration()) {
return parseExportDeclaration(true);
}
return parseAmbientDeclarationHelper();
}
private boolean peekImportDeclaration() {
return peek(TokenType.IMPORT)
&& (peekIdOrKeyword(1)
|| peek(1, TokenType.STRING)
|| peek(1, TokenType.OPEN_CURLY)
|| peek(1, TokenType.STAR));
}
private ParseTree parseImportDeclaration() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.IMPORT);
// import ModuleSpecifier ;
if (peek(TokenType.STRING)) {
LiteralToken moduleSpecifier = eat(TokenType.STRING).asLiteral();
eatPossiblyImplicitSemiColon();
return new ImportDeclarationTree(
getTreeLocation(start), null, null, null, moduleSpecifier);
}
// import ImportedDefaultBinding from ModuleSpecifier
// import NameSpaceImport from ModuleSpecifier
// import NamedImports from ModuleSpecifier ;
// import ImportedDefaultBinding , NameSpaceImport from ModuleSpecifier ;
// import ImportedDefaultBinding , NamedImports from ModuleSpecifier ;
IdentifierToken defaultBindingIdentifier = null;
IdentifierToken nameSpaceImportIdentifier = null;
ImmutableList identifierSet = null;
boolean parseExplicitNames = true;
if (peekId()) {
defaultBindingIdentifier = eatId();
if (peek(TokenType.COMMA)) {
eat(TokenType.COMMA);
} else {
parseExplicitNames = false;
}
} else if (Keywords.isKeyword(peekType())) {
Token keyword = nextToken();
reportError(keyword, "cannot use keyword '%s' here.", keyword);
}
if (parseExplicitNames) {
if (peek(TokenType.STAR)) {
eat(TokenType.STAR);
eatPredefinedString(PredefinedName.AS);
nameSpaceImportIdentifier = eatId();
} else {
identifierSet = parseImportSpecifierSet();
}
}
eatPredefinedString(PredefinedName.FROM);
Token moduleStr = eat(TokenType.STRING);
LiteralToken moduleSpecifier = (moduleStr == null)
? null : moduleStr.asLiteral();
eatPossiblyImplicitSemiColon();
return new ImportDeclarationTree(
getTreeLocation(start),
defaultBindingIdentifier, identifierSet, nameSpaceImportIdentifier, moduleSpecifier);
}
// ImportSpecifierSet ::= '{' (ImportSpecifier (',' ImportSpecifier)* (,)? )? '}'
private ImmutableList parseImportSpecifierSet() {
ImmutableList.Builder elements;
elements = ImmutableList.builder();
eat(TokenType.OPEN_CURLY);
while (peekIdOrKeyword()) {
elements.add(parseImportSpecifier());
if (!peek(TokenType.CLOSE_CURLY)) {
eat(TokenType.COMMA);
}
}
eat(TokenType.CLOSE_CURLY);
return elements.build();
}
// ImportSpecifier ::= Identifier ('as' Identifier)?
private ParseTree parseImportSpecifier() {
SourcePosition start = getTreeStartLocation();
IdentifierToken importedName = eatIdOrKeywordAsId();
IdentifierToken destinationName = null;
if (peekPredefinedString(PredefinedName.AS)) {
eatPredefinedString(PredefinedName.AS);
destinationName = eatId();
} else if (isKeyword(importedName.value)) {
reportExpectedError(null, PredefinedName.AS);
}
return new ImportSpecifierTree(
getTreeLocation(start), importedName, destinationName);
}
// export VariableStatement
// export FunctionDeclaration
// export ConstStatement
// export ClassDeclaration
// export default expression
// etc
private boolean peekExportDeclaration() {
return peek(TokenType.EXPORT);
}
/*
ExportDeclaration :
export * FromClause ;
export ExportClause [NoReference] FromClause ;
export ExportClause ;
export VariableStatement
export Declaration[Default]
export default AssignmentExpression ;
ExportClause [NoReference] :
{ }
{ ExportsList [?NoReference] }
{ ExportsList [?NoReference] , }
ExportsList [NoReference] :
ExportSpecifier [?NoReference]
ExportsList [?NoReference] , ExportSpecifier [?NoReference]
ExportSpecifier [NoReference] :
[~NoReference] IdentifierReference
[~NoReference] IdentifierReference as IdentifierName
[+NoReference] IdentifierName
[+NoReference] IdentifierName as IdentifierName
*/
private ParseTree parseExportDeclaration(boolean isAmbient) {
SourcePosition start = getTreeStartLocation();
boolean isDefault = false;
boolean isExportAll = false;
boolean isExportSpecifier = false;
boolean needsSemiColon = true;
eat(TokenType.EXPORT);
ParseTree export = null;
ImmutableList exportSpecifierList = null;
switch (peekType()) {
case STAR:
isExportAll = true;
nextToken();
break;
case IDENTIFIER:
export = parseAsyncFunctionDeclaration();
break;
case FUNCTION:
export = isAmbient ? parseAmbientFunctionDeclaration() : parseFunctionDeclaration();
needsSemiColon = isAmbient;
break;
case CLASS:
export = parseClassDeclaration(isAmbient);
needsSemiColon = false;
break;
case INTERFACE:
export = parseInterfaceDeclaration();
needsSemiColon = false;
break;
case ENUM:
export = parseEnumDeclaration();
needsSemiColon = false;
break;
case MODULE:
case NAMESPACE:
export = parseNamespaceDeclaration(isAmbient);
needsSemiColon = false;
break;
case DECLARE:
export = parseAmbientDeclaration();
needsSemiColon = false;
break;
case DEFAULT:
isDefault = true;
nextToken();
export = parseExpression();
needsSemiColon = false;
break;
case OPEN_CURLY:
isExportSpecifier = true;
exportSpecifierList = parseExportSpecifierSet();
break;
case TYPE:
export = parseTypeAlias();
break;
case VAR:
case LET:
case CONST:
default: // unreachable, parse as a var decl to get a parse error.
export = isAmbient ? parseAmbientVariableDeclarationList() : parseVariableDeclarationList();
break;
}
LiteralToken moduleSpecifier = null;
if (isExportAll || (isExportSpecifier && peekPredefinedString(PredefinedName.FROM))) {
eatPredefinedString(PredefinedName.FROM);
moduleSpecifier = (LiteralToken) eat(TokenType.STRING);
} else if (isExportSpecifier) {
for (ParseTree tree : exportSpecifierList) {
IdentifierToken importedName = tree.asExportSpecifier().importedName;
if (isKeyword(importedName.value)) {
reportError(importedName, "cannot use keyword '%s' here.", importedName.value);
}
}
}
if (needsSemiColon || peekImplicitSemiColon()) {
eatPossiblyImplicitSemiColon();
}
return new ExportDeclarationTree(
getTreeLocation(start), isDefault, isExportAll,
export, exportSpecifierList, moduleSpecifier);
}
// ExportSpecifierSet ::= '{' (ExportSpecifier (',' ExportSpecifier)* (,)? )? '}'
private ImmutableList parseExportSpecifierSet() {
ImmutableList.Builder elements;
elements = ImmutableList.builder();
eat(TokenType.OPEN_CURLY);
while (peekIdOrKeyword()) {
elements.add(parseExportSpecifier());
if (!peek(TokenType.CLOSE_CURLY)) {
eat(TokenType.COMMA);
}
}
eat(TokenType.CLOSE_CURLY);
return elements.build();
}
// ExportSpecifier ::= Identifier ('as' Identifier)?
private ParseTree parseExportSpecifier() {
SourcePosition start = getTreeStartLocation();
IdentifierToken importedName = eatIdOrKeywordAsId();
IdentifierToken destinationName = null;
if (peekPredefinedString(PredefinedName.AS)) {
eatPredefinedString(PredefinedName.AS);
destinationName = eatIdOrKeywordAsId();
}
return new ExportSpecifierTree(
getTreeLocation(start), importedName, destinationName);
}
private boolean peekClassDeclaration() {
return peek(TokenType.CLASS);
}
private boolean peekInterfaceDeclaration() {
return peek(TokenType.INTERFACE);
}
private boolean peekEnumDeclaration() {
return peek(TokenType.ENUM);
}
private boolean peekNamespaceDeclaration() {
return (peek(TokenType.MODULE) || peek(TokenType.NAMESPACE))
&& !peekImplicitSemiColon(1) && peek(1, TokenType.IDENTIFIER);
}
private ParseTree parseClassDeclaration(boolean isAmbient) {
return parseClass(false, isAmbient);
}
private ParseTree parseClassExpression() {
return parseClass(true, false);
}
private ParseTree parseInterfaceDeclaration() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.INTERFACE);
IdentifierToken name = eatId();
GenericTypeListTree generics = maybeParseGenericTypes();
ImmutableList.Builder superTypes = ImmutableList.builder();
if (peek(TokenType.EXTENDS)) {
eat(TokenType.EXTENDS);
ParseTree type = parseType();
superTypes.add(type);
while (peek(TokenType.COMMA)) {
eat(TokenType.COMMA);
type = parseType();
if (type != null) {
superTypes.add(type);
}
}
}
eat(TokenType.OPEN_CURLY);
ImmutableList elements = parseInterfaceElements();
eat(TokenType.CLOSE_CURLY);
return new InterfaceDeclarationTree(getTreeLocation(start), name, generics,
superTypes.build(), elements);
}
private ImmutableList parseInterfaceElements() {
ImmutableList.Builder result = ImmutableList.builder();
while (peekInterfaceElement()) {
result.add(parseInterfaceElement());
if (!peek(TokenType.CLOSE_CURLY)) {
// The standard delimiter is semicolon, but we also accept comma
if (peekImplicitSemiColon()) {
eatPossiblyImplicitSemiColon();
} else {
eat(TokenType.COMMA);
}
}
}
return result.build();
}
private boolean peekInterfaceElement() {
Token token = peekToken();
switch (token.type) {
case NEW:
case IDENTIFIER:
case OPEN_SQUARE:
case STAR:
case OPEN_ANGLE:
case OPEN_PAREN:
return true;
default:
return Keywords.isKeyword(token.type);
}
}
private ParseTree parseInterfaceElement() {
SourcePosition start = getTreeStartLocation();
boolean isGenerator = eatOpt(TokenType.STAR) != null;
IdentifierToken name = null;
TokenType type = peekType();
if (type == TokenType.NEW) {
return parseCallSignature(true); // ConstructSignature
} else if (type == TokenType.IDENTIFIER || Keywords.isKeyword(type)) {
name = eatIdOrKeywordAsId();
} else if (type == TokenType.OPEN_SQUARE) { // IndexSignature
return parseIndexSignature();
} else if (type == TokenType.OPEN_ANGLE || type == TokenType.OPEN_PAREN) { // CallSignature
return parseCallSignature(false);
}
boolean isOptional = false;
if (peek(TokenType.QUESTION)) {
eat(TokenType.QUESTION);
isOptional = true;
}
if (peek(TokenType.OPEN_PAREN) || peek(TokenType.OPEN_ANGLE)) {
// Method signature.
ParseTree function = parseMethodSignature(
start, name, false, isGenerator, isOptional, null);
return function;
} else {
// Property signature.
ParseTree declaredType = maybeParseColonType();
return new MemberVariableTree(
getTreeLocation(start), name, false, isOptional, null, declaredType);
}
}
private ParseTree parseEnumDeclaration() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.ENUM);
IdentifierToken name = eatId();
eat(TokenType.OPEN_CURLY);
ImmutableList members = parseEnumMembers();
eat(TokenType.CLOSE_CURLY);
return new EnumDeclarationTree(getTreeLocation(start), name, members);
}
private ImmutableList parseEnumMembers() {
SourceRange range = getTreeLocation(getTreeStartLocation());
IdentifierToken propertyName;
ParseTree member = null;
ImmutableList.Builder result = ImmutableList.builder();
while (peekId()) {
propertyName = parseIdentifierExpression().identifierToken;
member = new PropertyNameAssignmentTree(range, propertyName, null);
result.add(member);
if (!peek(TokenType.CLOSE_CURLY)) {
eat(TokenType.COMMA);
}
}
return result.build();
}
private ParseTree parseClass(boolean isExpression, boolean isAmbient) {
SourcePosition start = getTreeStartLocation();
eat(TokenType.CLASS);
IdentifierToken name = null;
if (!isExpression || peekId()) {
name = eatId();
}
GenericTypeListTree generics = maybeParseGenericTypes();
ParseTree superClass = null;
if (peek(TokenType.EXTENDS)) {
eat(TokenType.EXTENDS);
superClass = parseExpression();
}
ImmutableList.Builder interfaces = ImmutableList.builder();
if (config.parseTypeSyntax && peek(TokenType.IMPLEMENTS)) {
eat(TokenType.IMPLEMENTS);
ParseTree type = parseType();
interfaces.add(type);
while (peek(TokenType.COMMA)) {
eat(TokenType.COMMA);
type = parseType();
if (type != null) {
interfaces.add(type);
}
}
}
eat(TokenType.OPEN_CURLY);
ImmutableList elements = parseClassElements(isAmbient);
eat(TokenType.CLOSE_CURLY);
return new ClassDeclarationTree(getTreeLocation(start), name, generics,
superClass, interfaces.build(), elements);
}
private ImmutableList parseClassElements(boolean isAmbient) {
ImmutableList.Builder result = ImmutableList.builder();
while (true) {
Token token = peekToken();
switch (token.type) {
case SEMI_COLON:
// Ignore extraneous semicolons in class bodies.
eat(TokenType.SEMI_COLON);
continue;
default:
if (isClassElementStart(token)) {
break;
} else {
return result.build();
}
}
result.add(parseClassElement(isAmbient));
}
}
private boolean isClassElementStart(Token token) {
switch (token.type) {
case IDENTIFIER:
case NUMBER:
case BIGINT:
case STAR:
case STATIC:
case STRING:
case OPEN_SQUARE:
return true;
default:
if (Keywords.isKeyword(token.type)) {
return true;
}
}
return false;
}
private static class PartialClassElement {
final SourcePosition start;
boolean isAmbient = false;
boolean isStatic = false;
TokenType accessModifier = null;
PartialClassElement(SourcePosition start) {
this.start = start;
}
}
private PartialClassElement getClassElementDefaults() {
return new PartialClassElement(getTreeStartLocation());
}
private ParseTree parseClassElement(boolean isAmbient) {
if (peek(TokenType.SEMI_COLON)) {
return parseEmptyStatement();
} else {
PartialClassElement partialElement = getClassElementDefaults();
partialElement.isAmbient = isAmbient;
partialElement.accessModifier = maybeParseAccessibilityModifier();
partialElement.isStatic = eatStaticIfNotElementName();
return parseClassElement(partialElement);
}
}
private boolean eatStaticIfNotElementName() {
// only eat `static` if it being used as a keyword and not
// a member name.
if (peek(TokenType.STATIC) && isClassElementStart(peekToken(1))) {
eat(TokenType.STATIC);
return true;
}
return false;
}
private ParseTree parseClassElement(PartialClassElement partialElement) {
if (peekGetAccessor()) {
return parseGetAccessor(partialElement);
} else if (peekSetAccessor()) {
return parseSetAccessor(partialElement);
} else if (peekAsyncMethod()) {
return parseAsyncMethod(partialElement);
} else {
return parseClassMemberDeclaration(partialElement);
}
}
private boolean peekAsyncMethod() {
return peekPredefinedString(ASYNC)
&& !peekImplicitSemiColon(1)
&& (peekPropertyNameOrComputedProp(1)
|| (peek(1, TokenType.STAR) && peekPropertyNameOrComputedProp(2)));
}
private ParseTree parseClassMemberDeclaration() {
return parseClassMemberDeclaration(getClassElementDefaults());
}
private ParseTree parseClassMemberDeclaration(PartialClassElement partial) {
boolean isGenerator = eatOpt(TokenType.STAR) != null;
ParseTree nameExpr;
IdentifierToken name;
if (peekPropertyName(0)) {
if (peekIdOrKeyword()) {
nameExpr = null;
name = eatIdOrKeywordAsId();
if (Keywords.isKeyword(name.value, /* includeTypeScriptKeywords= */ false)) {
recordFeatureUsed(Feature.KEYWORDS_AS_PROPERTIES);
}
} else {
// { 'str'() {} }
// { 123() {} }
// Treat these as if they were computed properties.
// TODO(b/123769080): Stop making this assumption!
name = null;
nameExpr = parseLiteralExpression();
}
} else {
if (config.parseTypeSyntax && peekIndexSignature()) {
ParseTree indexSignature = parseIndexSignature();
eatPossiblyImplicitSemiColon();
return indexSignature;
}
nameExpr = parseComputedPropertyName();
name = null;
}
// Member variables are supported only when parsing type syntax
if (!config.parseTypeSyntax || peek(TokenType.OPEN_PAREN) || peek(TokenType.OPEN_ANGLE)) {
// Member function.
FunctionDeclarationTree.Kind kind;
TokenType accessOnFunction;
if (nameExpr == null) {
kind = FunctionDeclarationTree.Kind.MEMBER;
accessOnFunction = partial.accessModifier;
} else {
kind = FunctionDeclarationTree.Kind.EXPRESSION;
accessOnFunction = null; // Accessibility modifier goes on the ComputedPropertyMethodTree
}
ParseTree function;
if (partial.isAmbient) {
function = parseMethodSignature(partial, name, isGenerator, /* isOptional */ false);
eatPossiblyImplicitSemiColon();
} else {
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(kind)
.setName(name)
.setStatic(partial.isStatic)
.setAccess(accessOnFunction);
parseFunctionTail(builder, isGenerator ? FunctionFlavor.GENERATOR : FunctionFlavor.NORMAL);
function = builder.build(getTreeLocation(partial.start));
}
if (kind == FunctionDeclarationTree.Kind.MEMBER) {
return function;
} else {
return new ComputedPropertyMethodTree(
getTreeLocation(partial.start), partial.accessModifier, nameExpr, function);
}
} else {
// Member variable.
if (isGenerator) {
reportError("Member variable cannot be prefixed by '*' (generator function)");
}
ParseTree declaredType = maybeParseColonType();
if (peek(TokenType.EQUAL)) {
reportError("Member variable initializers ('=') are not supported");
}
eatPossiblyImplicitSemiColon();
if (nameExpr == null) {
return new MemberVariableTree(
getTreeLocation(partial.start),
name,
partial.isStatic,
false,
partial.accessModifier,
declaredType);
} else {
return new ComputedPropertyMemberVariableTree(
getTreeLocation(partial.start),
nameExpr,
partial.isStatic,
partial.accessModifier,
declaredType);
}
}
}
private ParseTree parseAsyncMethod() {
return parseAsyncMethod(getClassElementDefaults());
}
private ParseTree parseAsyncMethod(PartialClassElement partial) {
eatPredefinedString(ASYNC);
boolean generator = peek(TokenType.STAR);
if (generator) {
eat(TokenType.STAR);
}
if (peekPropertyName(0)) {
if (peekIdOrKeyword()) {
IdentifierToken name = eatIdOrKeywordAsId();
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.MEMBER)
.setAsync(true)
.setGenerator(generator)
.setStatic(partial.isStatic)
.setName(name)
.setAccess(partial.accessModifier);
if (partial.isAmbient) {
builder
.setGenerics(maybeParseGenericTypes())
.setFormalParameterList(parseFormalParameterList(ParamContext.SIGNATURE))
.setReturnType(maybeParseColonType())
.setFunctionBody(new EmptyStatementTree(getTreeLocation(partial.start)));
eatPossiblyImplicitSemiColon();
} else {
parseFunctionTail(
builder,
generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS);
}
return builder.build(getTreeLocation(name.getStart()));
} else {
// { 'str'() {} }
// { 123() {} }
// Treat these as if they were computed properties.
ParseTree nameExpr = parseLiteralExpression();
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.EXPRESSION)
.setAsync(true)
.setGenerator(generator)
.setStatic(partial.isStatic);
parseFunctionTail(
builder,
generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS);
ParseTree function = builder.build(getTreeLocation(nameExpr.getStart()));
return new ComputedPropertyMethodTree(
getTreeLocation(nameExpr.getStart()), partial.accessModifier, nameExpr, function);
}
} else if (config.parseTypeSyntax && peekIndexSignature()) {
ParseTree indexSignature = parseIndexSignature();
eatPossiblyImplicitSemiColon();
return indexSignature;
} else { // expect '[' to start computed property name
ParseTree nameExpr = parseComputedPropertyName();
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.EXPRESSION)
.setAsync(true)
.setGenerator(generator)
.setStatic(partial.isStatic);
parseFunctionTail(
builder, generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS);
ParseTree function = builder.build(getTreeLocation(nameExpr.getStart()));
return new ComputedPropertyMethodTree(
getTreeLocation(nameExpr.getStart()), partial.accessModifier, nameExpr, function);
}
}
private FunctionDeclarationTree parseMethodSignature(
PartialClassElement partial, IdentifierToken name, boolean isGenerator, boolean isOptional) {
return parseMethodSignature(
partial.start, name, partial.isStatic, isGenerator, isOptional, partial.accessModifier);
}
private FunctionDeclarationTree parseMethodSignature(
SourcePosition start,
IdentifierToken name,
boolean isStatic,
boolean isGenerator,
boolean isOptional,
TokenType access) {
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.MEMBER)
.setName(name)
.setStatic(isStatic)
.setGenerator(isGenerator)
.setOptional(isOptional)
.setAccess(access)
.setGenerics(maybeParseGenericTypes())
.setFormalParameterList(parseFormalParameterList(ParamContext.SIGNATURE))
.setReturnType(maybeParseColonType())
.setFunctionBody(new EmptyStatementTree(getTreeLocation(start)));
return builder.build(getTreeLocation(start));
}
private FunctionDeclarationTree parseAmbientFunctionDeclaration(
SourcePosition start, IdentifierToken name, boolean isGenerator) {
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.DECLARATION)
.setName(name)
.setGenerator(isGenerator)
.setGenerics(maybeParseGenericTypes())
.setFormalParameterList(parseFormalParameterList(ParamContext.SIGNATURE))
.setReturnType(maybeParseColonType())
.setFunctionBody(new EmptyStatementTree(getTreeLocation(start)));
return builder.build(getTreeLocation(start));
}
private void parseFunctionTail(
FunctionDeclarationTree.Builder builder, FunctionFlavor functionFlavor) {
functionContextStack.addLast(functionFlavor);
builder
.setGenerator(functionFlavor.isGenerator)
.setGenerics(maybeParseGenericTypes())
.setFormalParameterList(parseFormalParameterList(ParamContext.IMPLEMENTATION))
.setReturnType(maybeParseColonType())
.setFunctionBody(parseFunctionBody());
functionContextStack.removeLast();
}
private NamespaceDeclarationTree parseNamespaceDeclaration(boolean isAmbient) {
SourcePosition start = getTreeStartLocation();
if (eatOpt(TokenType.MODULE) == null) { // Accept "module" or "namespace"
eat(TokenType.NAMESPACE);
}
NamespaceNameTree name = parseNamespaceName();
eat(TokenType.OPEN_CURLY);
ImmutableList elements = isAmbient
? parseAmbientNamespaceElements() : parseNamespaceElements();
eat(TokenType.CLOSE_CURLY);
return new NamespaceDeclarationTree(getTreeLocation(start), name, elements);
}
private NamespaceNameTree parseNamespaceName() {
SourcePosition start = getTreeStartLocation();
IdentifierToken token = eatId();
return new NamespaceNameTree(getTreeLocation(start), buildIdentifierPath(token));
}
private ParseTree parseSourceElement() {
if (peekAsyncFunctionStart()) {
return parseAsyncFunctionDeclaration();
}
if (peekFunction()) {
return parseFunctionDeclaration();
}
if (peekClassDeclaration()) {
return parseClassDeclaration(false);
}
// Harmony let block scoped bindings. let can only appear in
// a block, not as a standalone statement: if() let x ... illegal
if (peek(TokenType.LET)) {
return parseVariableStatement();
}
// const and var are handled inside parseStatement
return parseStatementStandard();
}
private boolean peekSourceElement() {
return peekFunction() || peekStatementStandard() || peekDeclaration();
}
private boolean peekAsyncFunctionStart() {
return peekPredefinedString(ASYNC) && !peekImplicitSemiColon(1) && peekFunction(1);
}
private void eatAsyncFunctionStart() {
eatPredefinedString(ASYNC);
eat(TokenType.FUNCTION);
}
private boolean peekFunction() {
return peekFunction(0);
}
private boolean peekDeclaration() {
return peek(TokenType.LET) || peekClassDeclaration();
}
private boolean peekTypeAlias() {
return peek(TokenType.TYPE) && !peekImplicitSemiColon(1)
&& peek(1, TokenType.IDENTIFIER) && peek(2, TokenType.EQUAL);
}
private boolean peekIndexSignature() {
return peek(TokenType.OPEN_SQUARE) && peek(1, TokenType.IDENTIFIER)
&& peek(2, TokenType.COLON);
}
private IndexSignatureTree parseIndexSignature() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.OPEN_SQUARE);
IdentifierToken name = eatIdOrKeywordAsId();
eat(TokenType.COLON);
ParseTree indexType = parseTypeName(); // must be 'string' or 'number'
eat(TokenType.CLOSE_SQUARE);
eat(TokenType.COLON);
ParseTree declaredType = parseType();
ParseTree nameTree = new MemberVariableTree(getTreeLocation(start), name, false,
false, null, indexType);
return new IndexSignatureTree(getTreeLocation(start), nameTree, declaredType);
}
private CallSignatureTree parseCallSignature(boolean isNew) {
SourcePosition start = getTreeStartLocation();
if (isNew) {
eat(TokenType.NEW);
}
GenericTypeListTree generics = maybeParseGenericTypes();
FormalParameterListTree params = parseFormalParameterList(ParamContext.SIGNATURE);
ParseTree returnType = maybeParseColonType();
return new CallSignatureTree(getTreeLocation(start), isNew, generics, params, returnType);
}
private boolean peekAmbientDeclaration() {
return peek(TokenType.DECLARE) && !peekImplicitSemiColon(1)
&& (peek(1, TokenType.VAR)
|| peek(1, TokenType.LET)
|| peek(1, TokenType.CONST)
|| peek(1, TokenType.FUNCTION)
|| peek(1, TokenType.CLASS)
|| peek(1, TokenType.ENUM)
|| peek(1, TokenType.MODULE)
|| peek(1, TokenType.NAMESPACE));
}
private boolean peekAmbientNamespaceElement() {
return peek(TokenType.VAR)
|| peek(TokenType.LET)
|| peek(TokenType.CONST)
|| peek(TokenType.FUNCTION)
|| peek(TokenType.CLASS)
|| peek(TokenType.INTERFACE)
|| peek(TokenType.ENUM)
|| peek(TokenType.MODULE)
|| peek(TokenType.NAMESPACE)
|| peek(TokenType.EXPORT);
}
private boolean peekFunction(int index) {
return peek(index, TokenType.FUNCTION);
}
private boolean peekFunctionTypeExpression() {
if ((config.parseTypeSyntax && peek(TokenType.OPEN_PAREN)) || peek(TokenType.OPEN_ANGLE)) {
// TODO(blickly): determine if we can parse this without the
// overhead of forking the parser.
Parser p = createLookaheadParser();
try {
p.maybeParseGenericTypes();
p.parseFormalParameterList(ParamContext.TYPE_EXPRESSION);
if (p.peek(TokenType.COLON)) {
p.parseTypeAnnotation();
}
return p.peek(TokenType.ARROW);
} catch (ParseException e) {
return false;
}
}
return false;
}
// 13 Function Definition
private ParseTree parseFunctionDeclaration() {
SourcePosition start = getTreeStartLocation();
eat(Keywords.FUNCTION.type);
boolean isGenerator = eatOpt(TokenType.STAR) != null;
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.DECLARATION).setName(eatId());
parseFunctionTail(builder, isGenerator ? FunctionFlavor.GENERATOR : FunctionFlavor.NORMAL);
return builder.build(getTreeLocation(start));
}
private ParseTree parseFunctionExpression() {
SourcePosition start = getTreeStartLocation();
eat(Keywords.FUNCTION.type);
boolean isGenerator = eatOpt(TokenType.STAR) != null;
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.EXPRESSION)
.setName(eatIdOpt());
parseFunctionTail(builder, isGenerator ? FunctionFlavor.GENERATOR : FunctionFlavor.NORMAL);
return builder.build(getTreeLocation(start));
}
private ParseTree parseAsyncFunctionDeclaration() {
SourcePosition start = getTreeStartLocation();
eatAsyncFunctionStart();
boolean generator = peek(TokenType.STAR);
if (generator) {
eat(TokenType.STAR);
}
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.DECLARATION)
.setName(eatId())
.setAsync(true);
parseFunctionTail(
builder, generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS);
return builder.build(getTreeLocation(start));
}
private ParseTree parseAsyncFunctionExpression() {
SourcePosition start = getTreeStartLocation();
eatAsyncFunctionStart();
boolean generator = peek(TokenType.STAR);
if (generator) {
eat(TokenType.STAR);
}
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.EXPRESSION)
.setName(eatIdOpt())
.setAsync(true);
parseFunctionTail(
builder, generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS);
return builder.build(getTreeLocation(start));
}
private ParseTree parseAmbientFunctionDeclaration() {
SourcePosition start = getTreeStartLocation();
eat(Keywords.FUNCTION.type);
boolean isGenerator = eatOpt(TokenType.STAR) != null;
IdentifierToken name = eatId();
return parseAmbientFunctionDeclaration(start, name, isGenerator);
}
private enum ParamContext {
IMPLEMENTATION, // Normal function declaration or expression
// Allow destructuring and initializer
SIGNATURE, // TypeScript ambient function declaration or method signature
// Allow destructuring, disallow initializer
TYPE_EXPRESSION, // TypeScript colon types
// Disallow destructuring and initializer
}
private boolean peekParameter(ParamContext context) {
if (peekId() || peek(TokenType.ELLIPSIS)) {
return true;
}
if (context != ParamContext.TYPE_EXPRESSION) {
return peek(TokenType.OPEN_SQUARE) || peek(TokenType.OPEN_CURLY);
}
return false;
}
private ParseTree parseParameter(ParamContext context) {
SourcePosition start = getTreeStartLocation();
ParseTree parameter = null;
if (peek(TokenType.ELLIPSIS)) {
parameter = parseIterRest(PatternKind.INITIALIZER);
} else if (peekId()) {
parameter = parseIdentifierExpression();
if (peek(TokenType.QUESTION)) {
eat(TokenType.QUESTION);
parameter = new OptionalParameterTree(getTreeLocation(start), parameter);
}
} else if (context != ParamContext.TYPE_EXPRESSION && peekPatternStart()) {
parameter = parsePattern(PatternKind.INITIALIZER);
} else {
throw new IllegalStateException(
"parseParameterCalled() without confirming a parameter exists.");
}
ParseTree typeAnnotation = null;
SourceRange typeLocation = null;
if (peek(TokenType.COLON)) {
if (peek(1, TokenType.STRING)) {
eat(TokenType.COLON);
typeAnnotation = parseLiteralExpression(); // Specialized Signature
} else {
typeAnnotation = parseTypeAnnotation();
}
typeLocation = getTreeLocation(getTreeStartLocation());
}
if (context == ParamContext.IMPLEMENTATION
&& !parameter.isRestParameter()
&& peek(TokenType.EQUAL)) {
eat(TokenType.EQUAL);
ParseTree defaultValue = parseAssignmentExpression();
parameter = new DefaultParameterTree(getTreeLocation(start), parameter, defaultValue);
}
if (typeAnnotation != null) {
// Must be a direct child of the parameter list.
parameter = new TypedParameterTree(typeLocation, parameter, typeAnnotation);
}
return parameter;
}
private FormalParameterListTree parseFormalParameterList(ParamContext context) {
SourcePosition listStart = getTreeStartLocation();
eat(TokenType.OPEN_PAREN);
ImmutableList.Builder result = ImmutableList.builder();
boolean trailingComma = false;
ImmutableList.Builder commaPositions = ImmutableList.builder();
while (peekParameter(context)) {
result.add(parseParameter(context));
if (!peek(TokenType.CLOSE_PAREN)) {
Token comma = eat(TokenType.COMMA);
if (comma != null) {
commaPositions.add(comma.getStart());
}
if (peek(TokenType.CLOSE_PAREN)) {
recordFeatureUsed(Feature.TRAILING_COMMA_IN_PARAM_LIST);
if (!config.atLeast8) {
reportError(comma, "Invalid trailing comma in formal parameter list");
}
trailingComma = true;
}
}
}
eat(TokenType.CLOSE_PAREN);
return new FormalParameterListTree(
getTreeLocation(listStart), result.build(), trailingComma, commaPositions.build());
}
private FormalParameterListTree parseSetterParameterList() {
FormalParameterListTree parameterList = parseFormalParameterList(ParamContext.IMPLEMENTATION);
if (parameterList.parameters.size() != 1) {
reportError(
parameterList,
"Setter must have exactly 1 parameter, found %d",
parameterList.parameters.size());
}
if (parameterList.parameters.size() >= 1) {
ParseTree parameter = parameterList.parameters.get(0);
if (parameter.isRestParameter()) {
reportError(parameter, "Setter must not have a rest parameter");
}
}
return parameterList;
}
private ParseTree parseTypeAnnotation() {
eat(TokenType.COLON);
return parseType();
}
private ParseTree parseType() {
SourcePosition start = getTreeStartLocation();
if (!peekId() && !EnumSet.of(TokenType.VOID, TokenType.OPEN_PAREN, TokenType.OPEN_CURLY,
TokenType.TYPEOF).contains(peekType())) {
reportError("Unexpected token '%s' in type expression", peekType());
return new TypeNameTree(getTreeLocation(start), ImmutableList.of("error"));
}
ParseTree typeExpression = parseFunctionTypeExpression();
if (!peek(TokenType.BAR)) {
return typeExpression;
}
ImmutableList.Builder unionType = ImmutableList.builder();
unionType.add(typeExpression);
do {
eat(TokenType.BAR);
unionType.add(parseArrayTypeExpression());
} while (peek(TokenType.BAR));
return new UnionTypeTree(getTreeLocation(start), unionType.build());
}
private ParseTree parseFunctionTypeExpression() {
SourcePosition start = getTreeStartLocation();
ParseTree typeExpression = null;
if (peekFunctionTypeExpression()) {
FormalParameterListTree formalParameterList;
formalParameterList = parseFormalParameterList(ParamContext.IMPLEMENTATION);
eat(TokenType.ARROW);
ParseTree returnType = parseType();
typeExpression = new FunctionTypeTree(
getTreeLocation(start), formalParameterList, returnType);
} else {
typeExpression = parseArrayTypeExpression();
}
return typeExpression;
}
private ParseTree parseArrayTypeExpression() {
SourcePosition start = getTreeStartLocation();
ParseTree typeExpression = parseParenTypeExpression();
while (!peekImplicitSemiColon() && peek(TokenType.OPEN_SQUARE)) {
eat(TokenType.OPEN_SQUARE);
eat(TokenType.CLOSE_SQUARE);
typeExpression = new ArrayTypeTree(getTreeLocation(start), typeExpression);
}
return typeExpression;
}
private ParseTree parseParenTypeExpression() {
ParseTree typeExpression;
if (peek(TokenType.OPEN_PAREN)) {
eat(TokenType.OPEN_PAREN);
typeExpression = parseType();
eat(TokenType.CLOSE_PAREN);
} else {
typeExpression = parseRecordTypeExpression();
}
return typeExpression;
}
private ParseTree parseRecordTypeExpression() {
SourcePosition start = getTreeStartLocation();
ParseTree typeExpression;
if (peek(TokenType.OPEN_CURLY)) {
eat(TokenType.OPEN_CURLY);
typeExpression = new RecordTypeTree(getTreeLocation(start), parseInterfaceElements());
eat(TokenType.CLOSE_CURLY);
} else {
typeExpression = parseTypeQuery();
}
return typeExpression;
}
private ParseTree parseTypeQuery() {
SourcePosition start = getTreeStartLocation();
if (peek(TokenType.TYPEOF)) {
eat(TokenType.TYPEOF);
IdentifierToken token = eatId();
ImmutableList.Builder identifiers = ImmutableList.builder();
if (token != null) {
identifiers.add(token.value);
}
while (peek(TokenType.PERIOD)) {
// TypeQueryExpression . IdentifierName
eat(TokenType.PERIOD);
token = eatId();
if (token == null) {
break;
}
identifiers.add(token.value);
}
return new TypeQueryTree(getTreeLocation(start), identifiers.build());
} else {
return parseTypeReference();
}
}
private ParseTree parseTypeReference() {
SourcePosition start = getTreeStartLocation();
TypeNameTree typeName = parseTypeName();
if (!peek(TokenType.OPEN_ANGLE)) {
return typeName;
}
return parseTypeArgumentList(start, typeName);
}
private ParseTree parseTypeArgumentList(SourcePosition start, TypeNameTree typeName) {
// < TypeArgumentList >
// TypeArgumentList , TypeArgument
eat(TokenType.OPEN_ANGLE);
scanner.incTypeParameterLevel();
ImmutableList.Builder typeArguments = ImmutableList.builder();
ParseTree type = parseType();
typeArguments.add(type);
while (peek(TokenType.COMMA)) {
eat(TokenType.COMMA);
type = parseType();
if (type != null) {
typeArguments.add(type);
}
}
eat(TokenType.CLOSE_ANGLE);
scanner.decTypeParameterLevel();
return new ParameterizedTypeTree(getTreeLocation(start), typeName, typeArguments.build());
}
private TypeNameTree parseTypeName() {
SourcePosition start = getTreeStartLocation();
IdentifierToken token = eatIdOrKeywordAsId(); // for 'void'.
return new TypeNameTree(getTreeLocation(start), buildIdentifierPath(token));
}
private ImmutableList buildIdentifierPath(IdentifierToken token) {
ImmutableList.Builder identifiers = ImmutableList.builder();
identifiers.add(token != null ? token.value : ""); // null if errors while parsing
while (peek(TokenType.PERIOD)) {
// Namespace . Identifier
eat(TokenType.PERIOD);
token = eatId();
if (token == null) {
break;
}
identifiers.add(token.value);
}
return identifiers.build();
}
private BlockTree parseFunctionBody() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.OPEN_CURLY);
ImmutableList result = parseSourceElementList();
eat(TokenType.CLOSE_CURLY);
return new BlockTree(getTreeLocation(start), result);
}
private ImmutableList parseSourceElementList() {
ImmutableList.Builder result = ImmutableList.builder();
while (peekSourceElement()) {
result.add(parseSourceElement());
}
return result.build();
}
private IterSpreadTree parseIterSpread() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.ELLIPSIS);
ParseTree operand = parseAssignmentExpression();
return new IterSpreadTree(getTreeLocation(start), operand);
}
// 12 Statements
/**
* In V8, all source elements may appear where statements occur in the grammar.
*/
private ParseTree parseStatement() {
return parseSourceElement();
}
/**
* This function reflects the ECMA standard. Most places use peekStatement instead.
*/
private ParseTree parseStatementStandard() {
switch (peekType()) {
case OPEN_CURLY:
return parseBlock();
case CONST:
case VAR:
return parseVariableStatement();
case SEMI_COLON:
return parseEmptyStatement();
case IF:
return parseIfStatement();
case DO:
return parseDoWhileStatement();
case WHILE:
return parseWhileStatement();
case FOR:
return parseForStatement();
case CONTINUE:
return parseContinueStatement();
case BREAK:
return parseBreakStatement();
case RETURN:
return parseReturnStatement();
case WITH:
return parseWithStatement();
case SWITCH:
return parseSwitchStatement();
case THROW:
return parseThrowStatement();
case TRY:
return parseTryStatement();
case DEBUGGER:
return parseDebuggerStatement();
default:
if (peekLabelledStatement()) {
return parseLabelledStatement();
}
return parseExpressionStatement();
}
}
/**
* In V8 all source elements may appear where statements appear in the grammar.
*/
private boolean peekStatement() {
return peekSourceElement();
}
/**
* This function reflects the ECMA standard. Most places use peekStatement instead.
*/
private boolean peekStatementStandard() {
switch (peekType()) {
case OPEN_CURLY:
case VAR:
case CONST:
case SEMI_COLON:
case IF:
case DO:
case WHILE:
case FOR:
case CONTINUE:
case BREAK:
case RETURN:
case WITH:
case SWITCH:
case THROW:
case TRY:
case DEBUGGER:
case YIELD:
case IDENTIFIER:
case TYPE:
case DECLARE:
case MODULE:
case NAMESPACE:
case THIS:
case CLASS:
case SUPER:
case NUMBER:
case BIGINT:
case STRING:
case NO_SUBSTITUTION_TEMPLATE:
case TEMPLATE_HEAD:
case NULL:
case TRUE:
case SLASH: // regular expression literal
case SLASH_EQUAL: // regular expression literal
case FALSE:
case OPEN_SQUARE:
case OPEN_PAREN:
case NEW:
case DELETE:
case VOID:
case TYPEOF:
case PLUS_PLUS:
case MINUS_MINUS:
case PLUS:
case MINUS:
case TILDE:
case BANG:
return true;
default:
return false;
}
}
// 12.1 Block
private BlockTree parseBlock() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.OPEN_CURLY);
// Spec says Statement list. However functions are also embedded in the wild.
ImmutableList result = parseSourceElementList();
eat(TokenType.CLOSE_CURLY);
return new BlockTree(getTreeLocation(start), result);
}
private ImmutableList parseStatementList() {
ImmutableList.Builder result = ImmutableList.builder();
while (peekStatement()) {
result.add(parseStatement());
}
return result.build();
}
// 12.2 Variable Statement
private VariableStatementTree parseVariableStatement() {
SourcePosition start = getTreeStartLocation();
VariableDeclarationListTree declarations = parseVariableDeclarationList();
eatPossiblyImplicitSemiColon();
return new VariableStatementTree(getTreeLocation(start), declarations);
}
private VariableDeclarationListTree parseVariableDeclarationList() {
return parseVariableDeclarationList(Expression.NORMAL);
}
private VariableDeclarationListTree parseVariableDeclarationListNoIn() {
return parseVariableDeclarationList(Expression.NO_IN);
}
private VariableDeclarationListTree parseAmbientVariableDeclarationList() {
VariableDeclarationListTree declare = parseVariableDeclarationList(Expression.NO_IN);
// AmbientVariebleDeclaration may not have initializer
for (VariableDeclarationTree tree : declare.asVariableDeclarationList().declarations) {
if (tree.initializer != null) {
reportError("Ambient variable declaration may not have initializer");
}
}
return declare;
}
private VariableDeclarationListTree parseVariableDeclarationList(
Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
TokenType token = peekType();
switch (token) {
case CONST:
case LET:
case VAR:
eat(token);
break;
default:
reportError(peekToken(), "expected declaration");
return null;
}
ImmutableList.Builder declarations =
ImmutableList.builder();
declarations.add(parseVariableDeclaration(token, expressionIn));
while (peek(TokenType.COMMA)) {
eat(TokenType.COMMA);
declarations.add(parseVariableDeclaration(token, expressionIn));
}
return new VariableDeclarationListTree(
getTreeLocation(start), token, declarations.build());
}
private VariableDeclarationTree parseVariableDeclaration(
final TokenType binding, Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree lvalue;
ParseTree typeAnnotation = null;
if (peekPatternStart()) {
lvalue = parsePattern(PatternKind.INITIALIZER);
} else {
lvalue = parseIdentifierExpression();
if (peek(TokenType.COLON)) {
typeAnnotation = parseTypeAnnotation();
}
}
ParseTree initializer = null;
if (peek(TokenType.EQUAL)) {
initializer = parseInitializer(expressionIn);
} else if (expressionIn != Expression.NO_IN) {
// NOTE(blickly): this is a bit of a hack, declarations outside of for statements allow "in",
// and by chance, also must have initializers for const/destructuring. Vanilla for loops
// also require intializers, but are handled separately in checkVanillaForInitializers
maybeReportNoInitializer(binding, lvalue);
}
return new VariableDeclarationTree(getTreeLocation(start), lvalue, typeAnnotation, initializer);
}
private ParseTree parseInitializer(Expression expressionIn) {
eat(TokenType.EQUAL);
return parseAssignment(expressionIn);
}
// 12.3 Empty Statement
private EmptyStatementTree parseEmptyStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.SEMI_COLON);
return new EmptyStatementTree(getTreeLocation(start));
}
// 12.4 Expression Statement
private ExpressionStatementTree parseExpressionStatement() {
SourcePosition start = getTreeStartLocation();
ParseTree expression = parseExpression();
eatPossiblyImplicitSemiColon();
return new ExpressionStatementTree(getTreeLocation(start), expression);
}
// 12.5 If Statement
private IfStatementTree parseIfStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.IF);
eat(TokenType.OPEN_PAREN);
ParseTree condition = parseExpression();
eat(TokenType.CLOSE_PAREN);
ParseTree ifClause = parseStatement();
ParseTree elseClause = null;
if (peek(TokenType.ELSE)) {
eat(TokenType.ELSE);
elseClause = parseStatement();
}
return new IfStatementTree(getTreeLocation(start), condition, ifClause, elseClause);
}
// 12.6 Iteration Statements
// 12.6.1 The do-while Statement
private ParseTree parseDoWhileStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.DO);
ParseTree body = parseStatement();
eat(TokenType.WHILE);
eat(TokenType.OPEN_PAREN);
ParseTree condition = parseExpression();
eat(TokenType.CLOSE_PAREN);
// The semicolon after the "do-while" is optional.
if (peek(TokenType.SEMI_COLON)) {
eat(TokenType.SEMI_COLON);
}
return new DoWhileStatementTree(getTreeLocation(start), body, condition);
}
// 12.6.2 The while Statement
private ParseTree parseWhileStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.WHILE);
eat(TokenType.OPEN_PAREN);
ParseTree condition = parseExpression();
eat(TokenType.CLOSE_PAREN);
ParseTree body = parseStatement();
return new WhileStatementTree(getTreeLocation(start), condition, body);
}
// 12.6.3 The for Statement
// 12.6.4 The for-in Statement
// The for-of Statement
// The for-await-of Statement
private ParseTree parseForStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.FOR);
boolean awaited = peekPredefinedString(AWAIT);
if (awaited) {
eatPredefinedString(AWAIT);
}
eat(TokenType.OPEN_PAREN);
if (peekVariableDeclarationList()) {
VariableDeclarationListTree variables = parseVariableDeclarationListNoIn();
if (peek(TokenType.IN)) {
if (awaited) {
reportError("for-await-of is the only allowed asynchronous iteration");
}
// for-in: only one declaration allowed
if (variables.declarations.size() > 1) {
reportError("for-in statement may not have more than one variable declaration");
}
VariableDeclarationTree declaration = variables.declarations.get(0);
if (declaration.initializer != null) {
// An initializer is allowed here in ES5 and below, but not in ES6.
// Warn about it, to encourage people to eliminate it from their code.
// http://esdiscuss.org/topic/initializer-expression-on-for-in-syntax-subject
if (config.atLeast6) {
reportError("for-in statement may not have initializer");
} else {
errorReporter.reportWarning(declaration.location.start,
"for-in statement should not have initializer");
}
}
return parseForInStatement(start, variables);
} else if (peekPredefinedString(PredefinedName.OF)) {
// for-of: only one declaration allowed
if (variables.declarations.size() > 1) {
if (awaited) {
reportError("for-await-of statement may not have more than one variable declaration");
} else {
reportError("for-of statement may not have more than one variable declaration");
}
}
// for-of: initializer is illegal
VariableDeclarationTree declaration = variables.declarations.get(0);
if (declaration.initializer != null) {
if (awaited) {
reportError("for-await-of statement may not have initializer");
} else {
reportError("for-of statement may not have initializer");
}
}
if (awaited) {
return parseForAwaitOfStatement(start, variables);
} else {
return parseForOfStatement(start, variables);
}
} else {
// "Vanilla" for statement: const/destructuring must have initializer
checkVanillaForInitializers(variables);
return parseForStatement(start, variables);
}
}
if (peek(TokenType.SEMI_COLON)) {
return parseForStatement(start, null);
}
ParseTree initializer = parseExpressionNoIn();
if (peek(TokenType.IN) || peek(TokenType.EQUAL) || peekPredefinedString(PredefinedName.OF)) {
initializer = transformLeftHandSideExpression(initializer);
if (!initializer.isValidAssignmentTarget()) {
reportError("invalid assignment target");
}
}
if (peek(TokenType.IN) || peekPredefinedString(PredefinedName.OF)) {
if (initializer.type != ParseTreeType.BINARY_OPERATOR
&& initializer.type != ParseTreeType.COMMA_EXPRESSION) {
if (peek(TokenType.IN)) {
return parseForInStatement(start, initializer);
} else {
// for {await}? ( _ of _ )
if (awaited) {
return parseForAwaitOfStatement(start, initializer);
} else {
return parseForOfStatement(start, initializer);
}
}
}
}
return parseForStatement(start, initializer);
}
// The for-of Statement
// for ( { let | var }? identifier of expression ) statement
private ParseTree parseForOfStatement(
SourcePosition start, ParseTree initializer) {
eatPredefinedString(PredefinedName.OF);
ParseTree collection = parseExpression();
eat(TokenType.CLOSE_PAREN);
ParseTree body = parseStatement();
return new ForOfStatementTree(
getTreeLocation(start), initializer, collection, body);
}
private ParseTree parseForAwaitOfStatement(SourcePosition start, ParseTree initializer) {
eatPredefinedString(PredefinedName.OF);
ParseTree collection = parseExpression();
eat(TokenType.CLOSE_PAREN);
ParseTree body = parseStatement();
return new ForAwaitOfStatementTree(getTreeLocation(start), initializer, collection, body);
}
/** Checks variable declarations in for statements. */
private void checkVanillaForInitializers(VariableDeclarationListTree variables) {
for (VariableDeclarationTree declaration : variables.declarations) {
if (declaration.initializer == null) {
maybeReportNoInitializer(variables.declarationType, declaration.lvalue);
}
}
}
/** Reports if declaration requires an initializer, assuming initializer is absent. */
private void maybeReportNoInitializer(TokenType token, ParseTree lvalue) {
if (token == TokenType.CONST) {
reportError("const variables must have an initializer");
} else if (lvalue.isPattern()) {
reportError("destructuring must have an initializer");
}
}
private boolean peekVariableDeclarationList() {
switch (peekType()) {
case VAR:
case CONST:
case LET:
return true;
default:
return false;
}
}
// 12.6.3 The for Statement
private ParseTree parseForStatement(SourcePosition start, ParseTree initializer) {
if (initializer == null) {
initializer = new NullTree(new SourceRange(getTreeEndLocation(), getTreeStartLocation()));
}
eat(TokenType.SEMI_COLON);
ParseTree condition;
if (!peek(TokenType.SEMI_COLON)) {
condition = parseExpression();
} else {
condition = new NullTree(new SourceRange(getTreeEndLocation(), getTreeStartLocation()));
}
eat(TokenType.SEMI_COLON);
ParseTree increment;
if (!peek(TokenType.CLOSE_PAREN)) {
increment = parseExpression();
} else {
increment = new NullTree(new SourceRange(getTreeEndLocation(), getTreeStartLocation()));
}
eat(TokenType.CLOSE_PAREN);
ParseTree body = parseStatement();
return new ForStatementTree(getTreeLocation(start), initializer, condition, increment, body);
}
// 12.6.4 The for-in Statement
private ParseTree parseForInStatement(SourcePosition start, ParseTree initializer) {
eat(TokenType.IN);
ParseTree collection = parseExpression();
eat(TokenType.CLOSE_PAREN);
ParseTree body = parseStatement();
return new ForInStatementTree(getTreeLocation(start), initializer, collection, body);
}
// 12.7 The continue Statement
private ParseTree parseContinueStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.CONTINUE);
IdentifierToken name = null;
if (!peekImplicitSemiColon()) {
name = eatIdOpt();
}
eatPossiblyImplicitSemiColon();
return new ContinueStatementTree(getTreeLocation(start), name);
}
// 12.8 The break Statement
private ParseTree parseBreakStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.BREAK);
IdentifierToken name = null;
if (!peekImplicitSemiColon()) {
name = eatIdOpt();
}
eatPossiblyImplicitSemiColon();
return new BreakStatementTree(getTreeLocation(start), name);
}
// 12.9 The return Statement
private ParseTree parseReturnStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.RETURN);
ParseTree expression = null;
if (!peekImplicitSemiColon()) {
expression = parseExpression();
}
eatPossiblyImplicitSemiColon();
return new ReturnStatementTree(getTreeLocation(start), expression);
}
// 12.10 The with Statement
private ParseTree parseWithStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.WITH);
eat(TokenType.OPEN_PAREN);
ParseTree expression = parseExpression();
eat(TokenType.CLOSE_PAREN);
ParseTree body = parseStatement();
return new WithStatementTree(getTreeLocation(start), expression, body);
}
// 12.11 The switch Statement
private ParseTree parseSwitchStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.SWITCH);
eat(TokenType.OPEN_PAREN);
ParseTree expression = parseExpression();
eat(TokenType.CLOSE_PAREN);
eat(TokenType.OPEN_CURLY);
ImmutableList caseClauses = parseCaseClauses();
eat(TokenType.CLOSE_CURLY);
return new SwitchStatementTree(getTreeLocation(start), expression, caseClauses);
}
private ImmutableList parseCaseClauses() {
boolean foundDefaultClause = false;
ImmutableList.Builder result = ImmutableList.builder();
while (true) {
SourcePosition start = getTreeStartLocation();
switch (peekType()) {
case CASE:
eat(TokenType.CASE);
ParseTree expression = parseExpression();
eat(TokenType.COLON);
ImmutableList statements = parseCaseStatementsOpt();
result.add(new CaseClauseTree(getTreeLocation(start), expression, statements));
break;
case DEFAULT:
if (foundDefaultClause) {
reportError("Switch statements may have at most one default clause");
} else {
foundDefaultClause = true;
}
eat(TokenType.DEFAULT);
eat(TokenType.COLON);
result.add(new DefaultClauseTree(getTreeLocation(start), parseCaseStatementsOpt()));
break;
default:
return result.build();
}
}
}
private ImmutableList parseCaseStatementsOpt() {
return parseStatementList();
}
// 12.12 Labelled Statement
private ParseTree parseLabelledStatement() {
SourcePosition start = getTreeStartLocation();
IdentifierToken name = eatId();
eat(TokenType.COLON);
return new LabelledStatementTree(getTreeLocation(start), name, parseStatement());
}
private boolean peekLabelledStatement() {
return peekId()
&& peek(1, TokenType.COLON);
}
// 12.13 Throw Statement
private ParseTree parseThrowStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.THROW);
ParseTree value = null;
if (peekImplicitSemiColon()) {
reportError("semicolon/newline not allowed after 'throw'");
} else {
value = parseExpression();
}
eatPossiblyImplicitSemiColon();
return new ThrowStatementTree(getTreeLocation(start), value);
}
// 12.14 Try Statement
private ParseTree parseTryStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.TRY);
ParseTree body = parseBlock();
ParseTree catchBlock = null;
if (peek(TokenType.CATCH)) {
catchBlock = parseCatch();
}
ParseTree finallyBlock = null;
if (peek(TokenType.FINALLY)) {
finallyBlock = parseFinallyBlock();
}
if (catchBlock == null && finallyBlock == null) {
reportError("'catch' or 'finally' expected.");
}
return new TryStatementTree(getTreeLocation(start), body, catchBlock, finallyBlock);
}
private CatchTree parseCatch() {
SourcePosition start = getTreeStartLocation();
CatchTree catchBlock;
eat(TokenType.CATCH);
ParseTree exception =
new EmptyStatementTree(new SourceRange(getTreeEndLocation(), getTreeStartLocation()));
if (peekToken().type == TokenType.OPEN_PAREN) {
eat(TokenType.OPEN_PAREN);
if (peekPatternStart()) {
exception = parsePattern(PatternKind.INITIALIZER);
} else {
exception = parseIdentifierExpression();
}
eat(TokenType.CLOSE_PAREN);
} else {
recordFeatureUsed(Feature.OPTIONAL_CATCH_BINDING);
}
BlockTree catchBody = parseBlock();
catchBlock = new CatchTree(getTreeLocation(start), exception, catchBody);
return catchBlock;
}
private FinallyTree parseFinallyBlock() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.FINALLY);
BlockTree finallyBlock = parseBlock();
return new FinallyTree(getTreeLocation(start), finallyBlock);
}
// 12.15 The Debugger Statement
private ParseTree parseDebuggerStatement() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.DEBUGGER);
eatPossiblyImplicitSemiColon();
return new DebuggerStatementTree(getTreeLocation(start));
}
// 11.1 Primary Expressions
private ParseTree parsePrimaryExpression() {
switch (peekType()) {
case CLASS:
return parseClassExpression();
case SUPER:
return parseSuperExpression();
case THIS:
return parseThisExpression();
case IMPORT:
return parseDynamicImportExpression();
case IDENTIFIER:
case TYPE:
case DECLARE:
case MODULE:
case NAMESPACE:
return parseIdentifierExpression();
case NUMBER:
case STRING:
case BIGINT:
case TRUE:
case FALSE:
case NULL:
return parseLiteralExpression();
case NO_SUBSTITUTION_TEMPLATE:
case TEMPLATE_HEAD:
return parseTemplateLiteral(null);
case OPEN_SQUARE:
return parseArrayInitializer();
case OPEN_CURLY:
return parseObjectLiteral();
case OPEN_PAREN:
return parseCoverParenthesizedExpressionAndArrowParameterList();
case SLASH:
case SLASH_EQUAL:
return parseRegularExpressionLiteral();
default:
return parseMissingPrimaryExpression();
}
}
private SuperExpressionTree parseSuperExpression() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.SUPER);
if (peek(TokenType.QUESTION_DOT)) { // super?.() not allowed
reportError("Optional chaining is forbidden in super?.");
}
return new SuperExpressionTree(getTreeLocation(start));
}
private ThisExpressionTree parseThisExpression() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.THIS);
return new ThisExpressionTree(getTreeLocation(start));
}
// https://tc39.github.io/proposal-dynamic-import
private DynamicImportTree parseDynamicImportExpression() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.IMPORT);
if (peek(TokenType.QUESTION_DOT)) { // import?.() not allowed
reportError("Optional chaining is forbidden in import?.");
}
eat(TokenType.OPEN_PAREN);
ParseTree argument = parseAssignmentExpression();
eat(TokenType.CLOSE_PAREN);
recordFeatureUsed(Feature.DYNAMIC_IMPORT);
return new DynamicImportTree(getTreeLocation(start), argument);
}
private IdentifierExpressionTree parseIdentifierExpression() {
SourcePosition start = getTreeStartLocation();
IdentifierToken identifier = eatId();
return new IdentifierExpressionTree(getTreeLocation(start), identifier);
}
private LiteralExpressionTree parseLiteralExpression() {
SourcePosition start = getTreeStartLocation();
Token literal = nextLiteralToken();
if (literal.type == TokenType.STRING
&& ((StringLiteralToken) literal).hasUnescapedUnicodeLineOrParagraphSeparator()) {
recordFeatureUsed(Feature.UNESCAPED_UNICODE_LINE_OR_PARAGRAPH_SEP);
}
if (literal.type == TokenType.NUMBER && literal.toString().contains("_")) {
recordFeatureUsed(Feature.NUMERIC_SEPARATOR);
}
if (literal.type == TokenType.BIGINT) {
recordFeatureUsed(Feature.BIGINT);
}
return new LiteralExpressionTree(getTreeLocation(start), literal);
}
/**
* Constructs a template literal expression tree. "operand" is used to handle
* the case like "foo`bar`", which is a CallExpression or MemberExpression that
* calls the function foo() with the template literal as the argument (with extra
* handling). In this case, operand would be "foo", which is the callsite.
*
* We store this operand in the TemplateLiteralExpressionTree and
* generate a TAGGED_TEMPLATELIT node if it's not null later when transpiling.
*
* @param operand A non-null value would represent the callsite
* @return The template literal expression
*/
private TemplateLiteralExpressionTree parseTemplateLiteral(ParseTree operand) {
SourcePosition start = operand == null
? getTreeStartLocation()
: operand.location.start;
Token token = nextToken();
if (!(token instanceof TemplateLiteralToken)) {
reportError(token, "Unexpected template literal token %s.", token.type.toString());
}
boolean isTaggedTemplate = operand != null;
TemplateLiteralToken templateToken = (TemplateLiteralToken) token;
if (!isTaggedTemplate) {
reportTemplateErrorIfPresent(templateToken);
}
ImmutableList.Builder elements = ImmutableList.builder();
elements.add(new TemplateLiteralPortionTree(templateToken.location, templateToken));
if (templateToken.type == TokenType.NO_SUBSTITUTION_TEMPLATE) {
return new TemplateLiteralExpressionTree(
getTreeLocation(start), operand, elements.build());
}
// `abc${
ParseTree expression = parseExpression();
elements.add(new TemplateSubstitutionTree(expression.location, expression));
while (!errorReporter.hadError()) {
templateToken = nextTemplateLiteralToken();
if (templateToken.type == TokenType.ERROR || templateToken.type == TokenType.END_OF_FILE) {
break;
}
if (!isTaggedTemplate) {
reportTemplateErrorIfPresent(templateToken);
}
elements.add(new TemplateLiteralPortionTree(templateToken.location, templateToken));
if (templateToken.type == TokenType.TEMPLATE_TAIL) {
break;
}
expression = parseExpression();
elements.add(new TemplateSubstitutionTree(expression.location, expression));
}
return new TemplateLiteralExpressionTree(
getTreeLocation(start), operand, elements.build());
}
private Token nextLiteralToken() {
return nextToken();
}
private ParseTree parseRegularExpressionLiteral() {
SourcePosition start = getTreeStartLocation();
LiteralToken literal = nextRegularExpressionLiteralToken();
return new LiteralExpressionTree(getTreeLocation(start), literal);
}
private ParseTree parseArrayInitializer() {
if (peekType(1) == TokenType.FOR) {
return parseArrayComprehension();
} else {
return parseArrayLiteral();
}
}
private ParseTree parseGeneratorComprehension() {
return parseComprehension(
ComprehensionTree.ComprehensionType.GENERATOR,
TokenType.OPEN_PAREN, TokenType.CLOSE_PAREN);
}
private ParseTree parseArrayComprehension() {
return parseComprehension(
ComprehensionTree.ComprehensionType.ARRAY,
TokenType.OPEN_SQUARE, TokenType.CLOSE_SQUARE);
}
private ParseTree parseComprehension(
ComprehensionTree.ComprehensionType type,
TokenType startToken, TokenType endToken) {
SourcePosition start = getTreeStartLocation();
eat(startToken);
ImmutableList.Builder children = ImmutableList.builder();
while (peek(TokenType.FOR) || peek(TokenType.IF)) {
if (peek(TokenType.FOR)) {
children.add(parseComprehensionFor());
} else {
children.add(parseComprehensionIf());
}
}
ParseTree tailExpression = parseAssignmentExpression();
eat(endToken);
return new ComprehensionTree(
getTreeLocation(start),
type,
children.build(),
tailExpression);
}
private ParseTree parseComprehensionFor() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.FOR);
eat(TokenType.OPEN_PAREN);
ParseTree initializer;
if (peekId()) {
initializer = parseIdentifierExpression();
} else {
initializer = parsePattern(PatternKind.ANY);
}
eatPredefinedString(PredefinedName.OF);
ParseTree collection = parseAssignmentExpression();
eat(TokenType.CLOSE_PAREN);
return new ComprehensionForTree(
getTreeLocation(start), initializer, collection);
}
private ParseTree parseComprehensionIf() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.IF);
eat(TokenType.OPEN_PAREN);
ParseTree initializer = parseAssignmentExpression();
eat(TokenType.CLOSE_PAREN);
return new ComprehensionIfTree(
getTreeLocation(start), initializer);
}
// 11.1.4 Array Literal Expression
private ParseTree parseArrayLiteral() {
// ArrayLiteral :
// [ Elisionopt ]
// [ ElementList ]
// [ ElementList , Elisionopt ]
//
// ElementList :
// Elisionopt AssignmentOrSpreadExpression
// ElementList , Elisionopt AssignmentOrSpreadExpression
//
// Elision :
// ,
// Elision ,
SourcePosition start = getTreeStartLocation();
ImmutableList.Builder elements = ImmutableList.builder();
eat(TokenType.OPEN_SQUARE);
Token trailingCommaToken = null;
while (peek(TokenType.COMMA) || peek(TokenType.ELLIPSIS) || peekAssignmentExpression()) {
trailingCommaToken = null;
if (peek(TokenType.COMMA)) {
elements.add(new NullTree(getTreeLocation(getTreeStartLocation())));
} else {
if (peek(TokenType.ELLIPSIS)) {
recordFeatureUsed(Feature.SPREAD_EXPRESSIONS);
elements.add(parseIterSpread());
} else {
elements.add(parseAssignmentExpression());
}
}
if (!peek(TokenType.CLOSE_SQUARE)) {
trailingCommaToken = eat(TokenType.COMMA);
}
}
eat(TokenType.CLOSE_SQUARE);
maybeReportTrailingComma(trailingCommaToken);
return new ArrayLiteralExpressionTree(
getTreeLocation(start), elements.build(), trailingCommaToken != null);
}
// 11.1.4 Object Literal Expression
private ParseTree parseObjectLiteral() {
SourcePosition start = getTreeStartLocation();
ImmutableList.Builder result = ImmutableList.builder();
eat(TokenType.OPEN_CURLY);
Token commaToken = null;
while (peek(TokenType.ELLIPSIS)
|| peekPropertyNameOrComputedProp(0)
|| peek(TokenType.STAR)
|| peekAccessibilityModifier()) {
commaToken = null;
result.add(parsePropertyAssignment());
commaToken = eatOpt(TokenType.COMMA);
if (commaToken == null) {
break;
}
}
eat(TokenType.CLOSE_CURLY);
maybeReportTrailingComma(commaToken);
return new ObjectLiteralExpressionTree(
getTreeLocation(start), result.build(), commaToken != null);
}
void maybeReportTrailingComma(Token commaToken) {
if (commaToken != null) {
recordFeatureUsed(Feature.TRAILING_COMMA);
if (config.warnTrailingCommas) {
// In ES3 mode warn about trailing commas which aren't accepted by
// older browsers (such as IE8).
errorReporter.reportWarning(commaToken.location.start,
"Trailing comma is not legal in an ECMA-262 object initializer");
}
}
}
private boolean peekPropertyNameOrComputedProp(int tokenIndex) {
return peekPropertyName(tokenIndex)
|| peekType(tokenIndex) == TokenType.OPEN_SQUARE;
}
private boolean peekPropertyName(int tokenIndex) {
TokenType type = peekType(tokenIndex);
switch (type) {
case IDENTIFIER:
case STRING:
case NUMBER:
case BIGINT:
return true;
default:
return Keywords.isKeyword(type);
}
}
private ParseTree parsePropertyAssignment() {
TokenType type = peekType();
if (type == TokenType.STAR) {
return parsePropertyAssignmentGenerator();
} else if (type == TokenType.ELLIPSIS) {
recordFeatureUsed(Feature.OBJECT_LITERALS_WITH_SPREAD);
SourcePosition start = getTreeStartLocation();
eat(TokenType.ELLIPSIS);
ParseTree operand = parseAssignmentExpression();
return new ObjectSpreadTree(getTreeLocation(start), operand);
} else if (type == TokenType.STRING
|| type == TokenType.NUMBER
|| type == TokenType.BIGINT
|| type == TokenType.IDENTIFIER
|| Keywords.isKeyword(type)) {
if (peekGetAccessor()) {
return parseGetAccessor();
} else if (peekSetAccessor()) {
return parseSetAccessor();
} else if (peekAsyncMethod()) {
return parseAsyncMethod();
} else if (peekType(1) == TokenType.OPEN_PAREN) {
return parseClassMemberDeclaration();
} else {
return parsePropertyNameAssignment();
}
} else if (type == TokenType.OPEN_SQUARE) {
SourcePosition start = getTreeStartLocation();
ParseTree name = parseComputedPropertyName();
if (peek(TokenType.COLON)) {
eat(TokenType.COLON);
ParseTree value = parseAssignmentExpression();
return new ComputedPropertyDefinitionTree(getTreeLocation(start), name, value);
} else {
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.EXPRESSION);
parseFunctionTail(builder, FunctionFlavor.NORMAL);
ParseTree value = builder.build(getTreeLocation(start));
return new ComputedPropertyMethodTree(
getTreeLocation(start), null, name, value);
}
} else {
throw new RuntimeException("unreachable");
}
}
private ParseTree parsePropertyAssignmentGenerator() {
TokenType type = peekType(1);
if (type == TokenType.STRING
|| type == TokenType.NUMBER
|| type == TokenType.IDENTIFIER
|| Keywords.isKeyword(type)) {
// parseMethodDeclaration will consume the '*'.
return parseClassMemberDeclaration();
} else {
SourcePosition start = getTreeStartLocation();
eat(TokenType.STAR);
ParseTree name = parseComputedPropertyName();
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.EXPRESSION);
parseFunctionTail(builder, FunctionFlavor.GENERATOR);
ParseTree value = builder.build(getTreeLocation(start));
return new ComputedPropertyMethodTree(getTreeLocation(start), null, name, value);
}
}
private ParseTree parseComputedPropertyName() {
eat(TokenType.OPEN_SQUARE);
ParseTree assign = parseAssignmentExpression();
eat(TokenType.CLOSE_SQUARE);
return assign;
}
private boolean peekGetAccessor() {
return peekPredefinedString(PredefinedName.GET) && peekPropertyNameOrComputedProp(1);
}
private boolean peekPredefinedString(String string) {
return peekPredefinedString(0, string);
}
private Token eatPredefinedString(String string) {
Token token = eatId();
if (token == null || !token.asIdentifier().value.equals(string)) {
reportExpectedError(token, string);
return null;
}
return token;
}
private boolean peekPredefinedString(int index, String string) {
return peek(index, TokenType.IDENTIFIER)
&& ((IdentifierToken) peekToken(index)).value.equals(string);
}
private ParseTree parseGetAccessor() {
return parseGetAccessor(getClassElementDefaults());
}
private ParseTree parseGetAccessor(PartialClassElement partial) {
eatPredefinedString(PredefinedName.GET);
if (peekPropertyName(0)) {
Token propertyName = eatObjectLiteralPropertyName();
eat(TokenType.OPEN_PAREN);
eat(TokenType.CLOSE_PAREN);
ParseTree returnType = maybeParseColonType();
BlockTree body = parseFunctionBody();
recordFeatureUsed(Feature.GETTER);
return new GetAccessorTree(
getTreeLocation(partial.start), propertyName, partial.isStatic, returnType, body);
} else {
ParseTree property = parseComputedPropertyName();
eat(TokenType.OPEN_PAREN);
eat(TokenType.CLOSE_PAREN);
ParseTree returnType = maybeParseColonType();
BlockTree body = parseFunctionBody();
recordFeatureUsed(Feature.GETTER);
return new ComputedPropertyGetterTree(
getTreeLocation(partial.start),
property,
partial.isStatic,
partial.accessModifier,
returnType,
body);
}
}
private boolean peekSetAccessor() {
return peekPredefinedString(PredefinedName.SET) && peekPropertyNameOrComputedProp(1);
}
private ParseTree parseSetAccessor() {
return parseSetAccessor(getClassElementDefaults());
}
private ParseTree parseSetAccessor(PartialClassElement partial) {
eatPredefinedString(PredefinedName.SET);
if (peekPropertyName(0)) {
Token propertyName = eatObjectLiteralPropertyName();
FormalParameterListTree parameter = parseSetterParameterList();
ParseTree returnType = maybeParseColonType();
if (returnType != null) {
reportError(scanner.peekToken(), "setter should not have any returns");
}
BlockTree body = parseFunctionBody();
recordFeatureUsed(Feature.SETTER);
return new SetAccessorTree(
getTreeLocation(partial.start), propertyName, partial.isStatic, parameter, body);
} else {
ParseTree property = parseComputedPropertyName();
FormalParameterListTree parameter = parseSetterParameterList();
BlockTree body = parseFunctionBody();
recordFeatureUsed(Feature.SETTER);
return new ComputedPropertySetterTree(
getTreeLocation(partial.start),
property,
partial.isStatic,
partial.accessModifier,
parameter,
body);
}
}
private ParseTree parsePropertyNameAssignment() {
SourcePosition start = getTreeStartLocation();
Token name = eatObjectLiteralPropertyName();
Token colon = eatOpt(TokenType.COLON);
if (colon == null) {
if (name.type != TokenType.IDENTIFIER) {
reportExpectedError(peekToken(), TokenType.COLON);
} else if (Keywords.isKeyword(
name.asIdentifier().value, /* includeTypeScriptKeywords= */ false)) {
reportError(name, "Cannot use keyword in short object literal");
} else if (peek(TokenType.EQUAL)) {
IdentifierExpressionTree idTree = new IdentifierExpressionTree(
getTreeLocation(start), (IdentifierToken) name);
eat(TokenType.EQUAL);
ParseTree defaultValue = parseAssignmentExpression();
return new DefaultParameterTree(getTreeLocation(start), idTree, defaultValue);
}
}
ParseTree value = colon == null ? null : parseAssignmentExpression();
return new PropertyNameAssignmentTree(getTreeLocation(start), name, value);
}
// 12.2 Primary Expression
// CoverParenthesizedExpressionAndArrowParameterList ::=
// ( Expression )
// ( )
// ( ... BindingIdentifier )
// ( Expression , ... BindingIdentifier )
private ParseTree parseCoverParenthesizedExpressionAndArrowParameterList() {
if (peekType(1) == TokenType.FOR) {
return parseGeneratorComprehension();
}
SourcePosition start = getTreeStartLocation();
eat(TokenType.OPEN_PAREN);
// Case ( )
if (peek(TokenType.CLOSE_PAREN)) {
eat(TokenType.CLOSE_PAREN);
if (peek(TokenType.ARROW)) {
return new FormalParameterListTree(
getTreeLocation(start),
ImmutableList.of(),
/* hasTrailingComma= */ false,
ImmutableList.of());
} else {
reportError("invalid parenthesized expression");
return new MissingPrimaryExpressionTree(getTreeLocation(start));
}
}
// Case ( ... BindingIdentifier )
if (peek(TokenType.ELLIPSIS)) {
ImmutableList params = ImmutableList.of(
parseParameter(ParamContext.IMPLEMENTATION));
eat(TokenType.CLOSE_PAREN);
if (peek(TokenType.ARROW)) {
return new FormalParameterListTree(
getTreeLocation(start),
params,
/* hasTrailingComma= */ false,
ImmutableList.of());
} else {
reportError("invalid parenthesized expression");
return new MissingPrimaryExpressionTree(getTreeLocation(start));
}
}
// For either of the two remaining cases:
// ( Expression )
// ( Expression , ... BindingIdentifier )
// we can parse as an expression.
ParseTree result = parseExpression();
// If it follows witha comma, we must be in the
// ( Expression , ... BindingIdentifier )
// case.
if (peek(TokenType.COMMA)) {
eat(TokenType.COMMA);
// Since we already parsed as an expression, we will guaranteed reparse this expression
// as an arrow function parameter list, but just leave it as a comma expression for now.
result = new CommaExpressionTree(
getTreeLocation(start),
ImmutableList.of(result, parseParameter(ParamContext.IMPLEMENTATION)));
}
eat(TokenType.CLOSE_PAREN);
return new ParenExpressionTree(getTreeLocation(start), result);
}
private ParseTree parseMissingPrimaryExpression() {
SourcePosition start = getTreeStartLocation();
nextToken();
reportError("primary expression expected");
return new MissingPrimaryExpressionTree(getTreeLocation(start));
}
private GenericTypeListTree maybeParseGenericTypes() {
if (!peek(TokenType.OPEN_ANGLE)) {
return null;
}
SourcePosition start = getTreeStartLocation();
eat(TokenType.OPEN_ANGLE);
scanner.incTypeParameterLevel();
LinkedHashMap types = new LinkedHashMap<>();
do {
IdentifierToken name = eatId();
ParseTree bound = null;
if (peek(TokenType.EXTENDS)) {
eat(TokenType.EXTENDS);
bound = parseType();
}
types.put(name, bound);
if (peek(TokenType.COMMA)) {
eat(TokenType.COMMA);
}
} while (peekId());
eat(TokenType.CLOSE_ANGLE);
scanner.decTypeParameterLevel();
return new GenericTypeListTree(getTreeLocation(start), types);
}
private ParseTree maybeParseColonType() {
ParseTree type = null;
if (peek(TokenType.COLON)) {
type = parseTypeAnnotation();
}
return type;
}
/**
* Differentiates between parsing for 'In' vs. 'NoIn'
* Variants of expression grammars.
*/
private enum Expression {
NO_IN,
NORMAL,
}
// 11.14 Expressions
private ParseTree parseExpressionNoIn() {
return parse(Expression.NO_IN);
}
private ParseTree parseExpression() {
return parse(Expression.NORMAL);
}
private boolean peekExpression() {
switch (peekType()) {
case BANG:
case CLASS:
case DELETE:
case FALSE:
case FUNCTION:
case IDENTIFIER:
case TYPE:
case DECLARE:
case MODULE:
case NAMESPACE:
case MINUS:
case MINUS_MINUS:
case NEW:
case NULL:
case NUMBER:
case BIGINT:
case OPEN_CURLY:
case OPEN_PAREN:
case OPEN_SQUARE:
case PLUS:
case PLUS_PLUS:
case SLASH: // regular expression literal
case SLASH_EQUAL:
case STRING:
case NO_SUBSTITUTION_TEMPLATE:
case TEMPLATE_HEAD:
case SUPER:
case THIS:
case TILDE:
case TRUE:
case TYPEOF:
case VOID:
case YIELD:
return true;
case IMPORT:
return peekImportCall() || peekImportDot();
default:
return false;
}
}
private ParseTree parse(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree result = parseAssignment(expressionIn);
if (peek(TokenType.COMMA) && !peek(1, TokenType.ELLIPSIS)) {
ImmutableList.Builder exprs = ImmutableList.builder();
exprs.add(result);
while (peek(TokenType.COMMA) && !peek(1, TokenType.ELLIPSIS)) {
eat(TokenType.COMMA);
exprs.add(parseAssignment(expressionIn));
}
return new CommaExpressionTree(getTreeLocation(start), exprs.build());
}
return result;
}
// 12.14 Assignment operators
private ParseTree parseAssignmentExpression() {
return parseAssignment(Expression.NORMAL);
}
private boolean peekAssignmentExpression() {
return peekExpression();
}
private ParseTree parseAssignment(Expression expressionIn) {
if (peek(TokenType.YIELD) && inGeneratorContext()) {
return parseYield(expressionIn);
}
SourcePosition start = getTreeStartLocation();
// TODO(blickly): Allow TypeScript syntax in arrow function parameters
ParseTree left = parseConditional(expressionIn);
if (isStartOfAsyncArrowFunction(left)) {
// re-evaluate as an async arrow function.
resetScanner(left);
return parseAsyncArrowFunction(expressionIn);
}
if (peek(TokenType.ARROW)) {
return completeAssignmentExpressionParseAtArrow(left, expressionIn);
}
if (peekAssignmentOperator()) {
left = transformLeftHandSideExpression(left);
if (!left.isValidAssignmentTarget()) {
reportError("invalid assignment target");
return new MissingPrimaryExpressionTree(getTreeLocation(getTreeStartLocation()));
}
Token operator = nextToken();
ParseTree right = parseAssignment(expressionIn);
return new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
private boolean isStartOfAsyncArrowFunction(ParseTree partialExpression) {
if (partialExpression.type == ParseTreeType.IDENTIFIER_EXPRESSION) {
final IdentifierToken identifierToken =
partialExpression.asIdentifierExpression().identifierToken;
// partialExpression is `async`
// followed by `[no newline] bindingIdentifier [no newline] =>`
return identifierToken.value.equals(ASYNC)
&& !peekImplicitSemiColon(0)
&& peekId()
&& !peekImplicitSemiColon(1)
&& peek(1, TokenType.ARROW);
} else if (partialExpression.type == ParseTreeType.CALL_EXPRESSION) {
final CallExpressionTree callExpression = partialExpression.asCallExpression();
ParseTree callee = callExpression.operand;
ParseTree arguments = callExpression.arguments;
// partialExpression is `async [no newline] (parameters)`
// followed by `[no newline] =>`
return callee.type == ParseTreeType.IDENTIFIER_EXPRESSION
&& callee.asIdentifierExpression().identifierToken.value.equals(ASYNC)
&& callee.location.end.line == arguments.location.start.line
&& !peekImplicitSemiColon()
&& peek(TokenType.ARROW);
} else {
return false;
}
}
private ParseTree completeAssignmentExpressionParseAtArrow(
ParseTree leftOfArrow, Expression expressionIn) {
if (leftOfArrow.type == ParseTreeType.CALL_EXPRESSION) {
// ... someAssignmentExpression // implicit semicolon
// (args) =>
return completeAssignmentExpressionParseAtArrow(leftOfArrow.asCallExpression());
} else {
return completeArrowFunctionParseAtArrow(leftOfArrow, expressionIn);
}
}
private ParseTree completeArrowFunctionParseAtArrow(
ParseTree leftOfArrow, Expression expressionIn) {
FormalParameterListTree arrowFormalParameters = transformToArrowFormalParameters(leftOfArrow);
if (peekImplicitSemiColon()) {
reportError("No newline allowed before '=>'");
}
eat(TokenType.ARROW);
ParseTree arrowFunctionBody = parseArrowFunctionBody(expressionIn, FunctionFlavor.NORMAL);
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.ARROW)
.setFormalParameterList(arrowFormalParameters)
.setFunctionBody(arrowFunctionBody);
return builder.build(getTreeLocation(arrowFormalParameters.location.start));
}
private FormalParameterListTree transformToArrowFormalParameters(ParseTree leftOfArrow) {
FormalParameterListTree arrowParameterList;
switch (leftOfArrow.type) {
case FORMAL_PARAMETER_LIST:
arrowParameterList = leftOfArrow.asFormalParameterList();
break;
case IDENTIFIER_EXPRESSION:
// e.g. x => x + 1
arrowParameterList =
new FormalParameterListTree(
leftOfArrow.location,
ImmutableList.of(leftOfArrow),
/* hasTrailingComma= */ false,
ImmutableList.of());
break;
case ARGUMENT_LIST:
case PAREN_EXPRESSION:
// e.g. (x) => x + 1
resetScanner(leftOfArrow);
// If we fail to parse as an ArrowFunction parameter list then
// parseFormalParameterList will take care of reporting errors.
arrowParameterList = parseFormalParameterList(ParamContext.IMPLEMENTATION);
break;
default:
reportError(leftOfArrow, "invalid arrow function parameters");
arrowParameterList = newEmptyFormalParameterList(leftOfArrow.location);
}
return arrowParameterList;
}
private ParseTree completeAssignmentExpressionParseAtArrow(CallExpressionTree callExpression) {
ParseTree operand = callExpression.operand;
ParseTree arguments = callExpression.arguments;
ParseTree result;
if (operand.location.end.line < arguments.location.start.line) {
// break at the implicit semicolon
// Example:
// foo.bar // operand and implicit semicolon
// () => { doSomething; };
resetScannerAfter(operand);
result = operand;
} else {
reportError("'=>' unexpected");
result = callExpression;
}
return result;
}
private ParseTree parseAsyncArrowFunction(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
eatPredefinedString(ASYNC);
if (peekImplicitSemiColon()) {
reportError("No newline allowed between `async` and arrow function parameter list");
}
FormalParameterListTree arrowParameterList = null;
if (peek(TokenType.OPEN_PAREN)) {
// async (...) =>
arrowParameterList = parseFormalParameterList(ParamContext.IMPLEMENTATION);
} else {
// async arg =>
final IdentifierExpressionTree singleParameter = parseIdentifierExpression();
arrowParameterList =
new FormalParameterListTree(
singleParameter.location,
ImmutableList.of(singleParameter),
/* hasTrailingComma= */ false,
ImmutableList.of());
}
if (peekImplicitSemiColon()) {
reportError("No newline allowed before '=>'");
}
eat(TokenType.ARROW);
ParseTree arrowFunctionBody = parseArrowFunctionBody(expressionIn, FunctionFlavor.ASYNCHRONOUS);
FunctionDeclarationTree.Builder builder =
FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.ARROW)
.setAsync(true)
.setFormalParameterList(arrowParameterList)
.setFunctionBody(arrowFunctionBody);
return builder.build(getTreeLocation(start));
}
private ParseTree parseArrowFunctionBody(Expression expressionIn, FunctionFlavor functionFlavor) {
functionContextStack.addLast(functionFlavor);
ParseTree arrowFunctionBody;
if (peek(TokenType.OPEN_CURLY)) {
arrowFunctionBody = parseFunctionBody();
} else {
arrowFunctionBody = parseAssignment(expressionIn);
}
functionContextStack.removeLast();
return arrowFunctionBody;
}
private static FormalParameterListTree newEmptyFormalParameterList(SourceRange location) {
return new FormalParameterListTree(
location,
ImmutableList.of(),
/* hasTrailingComma= */ false,
ImmutableList.of());
}
/**
* Transforms a LeftHandSideExpression into a LeftHandSidePattern if possible.
* This returns the transformed tree if it parses as a LeftHandSidePattern,
* otherwise it returns the original tree.
*/
private ParseTree transformLeftHandSideExpression(ParseTree tree) {
switch (tree.type) {
case ARRAY_LITERAL_EXPRESSION:
case OBJECT_LITERAL_EXPRESSION:
resetScanner(tree);
// If we fail to parse as an LeftHandSidePattern then
// parseLeftHandSidePattern will take care reporting errors.
return parseLeftHandSidePattern();
default:
return tree;
}
}
private ParseTree parseLeftHandSidePattern() {
return parsePattern(PatternKind.ANY);
}
private void resetScanner(ParseTree tree) {
// TODO(bradfordcsmith): lastSourcePosition should really point to the end of the last token
// before the tree to correctly detect implicit semicolons, but it doesn't matter for the
// current use case.
lastSourcePosition = tree.location.start;
scanner.setPosition(lastSourcePosition);
}
private void resetScannerAfter(ParseTree parseTree) {
lastSourcePosition = parseTree.location.end;
// NOTE: The "end" position for a parseTree actually points to the first character after the
// last token in the tree, so this is not an off-by-one error.
scanner.setPosition(lastSourcePosition);
}
private boolean peekAssignmentOperator() {
switch (peekType()) {
case EQUAL:
case STAR_EQUAL:
case STAR_STAR_EQUAL:
case SLASH_EQUAL:
case PERCENT_EQUAL:
case PLUS_EQUAL:
case MINUS_EQUAL:
case LEFT_SHIFT_EQUAL:
case RIGHT_SHIFT_EQUAL:
case UNSIGNED_RIGHT_SHIFT_EQUAL:
case AMPERSAND_EQUAL:
case CARET_EQUAL:
case BAR_EQUAL:
return true;
default:
return false;
}
}
private boolean inGeneratorContext() {
// disallow yield outside of generators
return functionContextStack.peekLast().isGenerator;
}
// yield [no line terminator] (*)? AssignExpression
// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generator-function-definitions-runtime-semantics-evaluation
private ParseTree parseYield(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
eat(TokenType.YIELD);
boolean isYieldAll = false;
ParseTree expression = null;
if (!peekImplicitSemiColon()) {
isYieldAll = eatOpt(TokenType.STAR) != null;
if (peekAssignmentExpression()) {
expression = parseAssignment(expressionIn);
} else if (isYieldAll) {
reportError("yield* requires an expression");
}
}
return new YieldExpressionTree(
getTreeLocation(start), isYieldAll, expression);
}
// 11.12 Conditional Expression
private ParseTree parseConditional(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree condition = parseShortCircuit(expressionIn);
if (peek(TokenType.QUESTION)) {
eat(TokenType.QUESTION);
ParseTree left = parseAssignment(expressionIn);
eat(TokenType.COLON);
ParseTree right = parseAssignment(expressionIn);
return new ConditionalExpressionTree(
getTreeLocation(start), condition, left, right);
}
return condition;
}
private ParseTree parseShortCircuit(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseLogicalOR(expressionIn);
if (peek(TokenType.QUESTION_QUESTION)) {
if (left.type == ParseTreeType.BINARY_OPERATOR) {
BinaryOperatorTree binaryTree = left.asBinaryOperator();
if (binaryTree.operator.type == TokenType.AND || binaryTree.operator.type == TokenType.OR) {
reportError("Logical OR and logical AND require parentheses when used with '??'");
}
}
return parseNullishCoalesce(expressionIn, left, start);
} else {
return left;
}
}
private ParseTree parseNullishCoalesce(
Expression expressionIn, ParseTree left, SourcePosition start) {
while (peek(TokenType.QUESTION_QUESTION)) {
Token operator = eat(TokenType.QUESTION_QUESTION);
ParseTree right = parseBitwiseOR(expressionIn);
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
if (peek(TokenType.AND) || peek(TokenType.OR)) {
reportError("Logical OR and logical AND require parentheses when used with '??'");
}
return left;
}
// 11.11 Logical OR
private ParseTree parseLogicalOR(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseLogicalAND(expressionIn);
while (peek(TokenType.OR)) {
Token operator = eat(TokenType.OR);
ParseTree right = parseLogicalAND(expressionIn);
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
// 11.11 Logical AND
private ParseTree parseLogicalAND(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseBitwiseOR(expressionIn);
while (peek(TokenType.AND)) {
Token operator = eat(TokenType.AND);
ParseTree right = parseBitwiseOR(expressionIn);
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
// 11.10 Bitwise OR
private ParseTree parseBitwiseOR(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseBitwiseXOR(expressionIn);
while (peek(TokenType.BAR)) {
Token operator = eat(TokenType.BAR);
ParseTree right = parseBitwiseXOR(expressionIn);
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
// 11.10 Bitwise XOR
private ParseTree parseBitwiseXOR(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseBitwiseAND(expressionIn);
while (peek(TokenType.CARET)) {
Token operator = eat(TokenType.CARET);
ParseTree right = parseBitwiseAND(expressionIn);
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
// 11.10 Bitwise AND
private ParseTree parseBitwiseAND(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseEquality(expressionIn);
while (peek(TokenType.AMPERSAND)) {
Token operator = eat(TokenType.AMPERSAND);
ParseTree right = parseEquality(expressionIn);
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
// 11.9 Equality Expression
private ParseTree parseEquality(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseRelational(expressionIn);
while (peekEqualityOperator()) {
Token operator = nextToken();
ParseTree right = parseRelational(expressionIn);
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
private boolean peekEqualityOperator() {
switch (peekType()) {
case EQUAL_EQUAL:
case NOT_EQUAL:
case EQUAL_EQUAL_EQUAL:
case NOT_EQUAL_EQUAL:
return true;
default:
return false;
}
}
// 11.8 Relational
private ParseTree parseRelational(Expression expressionIn) {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseShiftExpression();
while (peekRelationalOperator(expressionIn)) {
Token operator = nextToken();
ParseTree right = parseShiftExpression();
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
private boolean peekRelationalOperator(Expression expressionIn) {
switch (peekType()) {
case OPEN_ANGLE:
case CLOSE_ANGLE:
case GREATER_EQUAL:
case LESS_EQUAL:
case INSTANCEOF:
return true;
case IN:
return expressionIn == Expression.NORMAL;
default:
return false;
}
}
// 11.7 Shift Expression
private ParseTree parseShiftExpression() {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseAdditiveExpression();
while (peekShiftOperator()) {
Token operator = nextToken();
ParseTree right = parseAdditiveExpression();
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
private boolean peekShiftOperator() {
switch (peekType()) {
case LEFT_SHIFT:
case RIGHT_SHIFT:
case UNSIGNED_RIGHT_SHIFT:
return true;
default:
return false;
}
}
// 11.6 Additive Expression
private ParseTree parseAdditiveExpression() {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseMultiplicativeExpression();
while (peekAdditiveOperator()) {
Token operator = nextToken();
ParseTree right = parseMultiplicativeExpression();
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
private boolean peekAdditiveOperator() {
switch (peekType()) {
case PLUS:
case MINUS:
return true;
default:
return false;
}
}
// 11.5 Multiplicative Expression
private ParseTree parseMultiplicativeExpression() {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseExponentiationExpression();
while (peekMultiplicativeOperator()) {
Token operator = nextToken();
ParseTree right = parseExponentiationExpression();
left = new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
return left;
}
private boolean peekMultiplicativeOperator() {
switch (peekType()) {
case STAR:
case SLASH:
case PERCENT:
return true;
default:
return false;
}
}
private ParseTree parseExponentiationExpression() {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseUnaryExpression();
if (peek(TokenType.STAR_STAR)) {
// ExponentiationExpression does not allow a UnaryExpression before '**'.
// Parentheses are required to disambiguate:
// (-x)**y is valid
// -(x**y) is valid
// -x**y is a syntax error
if (left.type == ParseTreeType.UNARY_EXPRESSION) {
reportError(
"Unary operator '%s' requires parentheses before '**'",
left.asUnaryExpression().operator);
}
Token operator = nextToken();
ParseTree right = parseExponentiationExpression();
return new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
} else {
return left;
}
}
// 11.4 Unary Operator
private ParseTree parseUnaryExpression() {
SourcePosition start = getTreeStartLocation();
if (peekUnaryOperator()) {
Token operator = nextToken();
ParseTree operand = parseUnaryExpression();
return new UnaryExpressionTree(getTreeLocation(start), operator, operand);
} else if (peekAwaitExpression()) {
return parseAwaitExpression();
} else {
return parseUpdateExpression();
}
}
private boolean peekUnaryOperator() {
switch (peekType()) {
case DELETE:
case VOID:
case TYPEOF:
case PLUS:
case MINUS:
case TILDE:
case BANG:
return true;
default:
return false;
}
}
private static final String AWAIT = "await";
private boolean peekAwaitExpression() {
return peekPredefinedString(AWAIT);
}
private ParseTree parseAwaitExpression() {
SourcePosition start = getTreeStartLocation();
if (functionContextStack.isEmpty() || !functionContextStack.peekLast().isAsynchronous) {
reportError("'await' used in a non-async function context");
}
eatPredefinedString(AWAIT);
ParseTree expression = parseUnaryExpression();
return new AwaitExpressionTree(getTreeLocation(start), expression);
}
private ParseTree parseUpdateExpression() {
SourcePosition start = getTreeStartLocation();
if (peekUpdateOperator()) {
Token operator = nextToken();
ParseTree operand = parseUnaryExpression();
return UpdateExpressionTree.prefix(getTreeLocation(start), operator, operand);
} else {
ParseTree lhs = parseLeftHandSideExpression();
if (peekUpdateOperator() && !peekImplicitSemiColon()) {
// newline not allowed before an update operator.
Token operator = nextToken();
return UpdateExpressionTree.postfix(getTreeLocation(start), operator, lhs);
} else {
return lhs;
}
}
}
private boolean peekUpdateOperator() {
switch (peekType()) {
case PLUS_PLUS:
case MINUS_MINUS:
return true;
default:
return false;
}
}
private boolean peekImportCall() {
return peek(TokenType.IMPORT) && peek(1, TokenType.OPEN_PAREN);
}
private boolean peekImportDot() {
return peek(TokenType.IMPORT) && peek(1, TokenType.PERIOD);
}
/** Parse LeftHandSideExpression. */
@SuppressWarnings("incomplete-switch")
private ParseTree parseLeftHandSideExpression() {
SourcePosition start = getTreeStartLocation();
// We have these possible productions.
// LeftHandSideExpression -> NewExpression
// -> CallExpression
// -> MemberExpression
// -> OptionalExpression
//
// NewExpression -> new NewExpression
// -> MemberExpression
//
// CallExpression -> MemberExpression Arguments
// -> CallExpression ... see below
//
// OptionalExpression -> MemberExpression OptionalChain
// -> CallExpression OptionalChain
// -> OptionalExpression OptionalChain
//
// We try parsing a NewExpression, here, because that will include parsing MemberExpression.
// If what we really have is a CallExpression or OptionalExpression, then the MemberExpression
// we get back from parseNewExpression will be the first part of it, and we'll build the
// rest later.
ParseTree operand = parseNewExpression();
// this test is equivalent to is member expression
if (!(operand instanceof NewExpressionTree)
|| ((NewExpressionTree) operand).arguments != null) {
// We have a MemberExpression, but it may actually be just the first part of a CallExpression
// Attempt to gather the rest of the CallExpression, if so.
while (peekCallSuffix()) {
switch (peekType()) {
case OPEN_PAREN:
ArgumentListTree arguments = parseArguments();
operand = new CallExpressionTree(getTreeLocation(start), operand, arguments);
break;
case OPEN_SQUARE:
eat(TokenType.OPEN_SQUARE);
ParseTree member = parseExpression();
eat(TokenType.CLOSE_SQUARE);
operand = new MemberLookupExpressionTree(getTreeLocation(start), operand, member);
break;
case PERIOD:
eat(TokenType.PERIOD);
IdentifierToken id = eatIdOrKeywordAsId();
operand = new MemberExpressionTree(getTreeLocation(start), operand, id);
break;
case NO_SUBSTITUTION_TEMPLATE:
case TEMPLATE_HEAD:
operand = parseTemplateLiteral(operand);
break;
default:
throw new AssertionError("unexpected case: " + peekType());
}
}
operand = maybeParseOptionalExpression(operand);
}
return operand;
}
private boolean peekCallSuffix() {
return peek(TokenType.OPEN_PAREN)
|| peek(TokenType.OPEN_SQUARE)
|| peek(TokenType.PERIOD)
|| peek(TokenType.NO_SUBSTITUTION_TEMPLATE)
|| peek(TokenType.TEMPLATE_HEAD);
}
/**
* Tries to parse the expression as an optional expression.
*
* `operand?.identifier` or `operand?.[expression]` or `operand?.(arg1, arg2)`
*
*
returns parse tree after trying to parse it as an optional expression
*/
private ParseTree maybeParseOptionalExpression(ParseTree operand) {
// The optional chain's source info should cover the lhs operand also
SourcePosition start = operand.location.start;
while (peek(TokenType.QUESTION_DOT)) {
eat(TokenType.QUESTION_DOT);
switch (peekType()) {
case OPEN_PAREN:
ArgumentListTree arguments = parseArguments();
operand =
new OptionalCallExpressionTree(
getTreeLocation(start),
operand,
arguments,
/* isStartOfOptionalChain = */ true,
arguments.hasTrailingComma);
break;
case OPEN_SQUARE:
eat(TokenType.OPEN_SQUARE);
ParseTree member = parseExpression();
eat(TokenType.CLOSE_SQUARE);
operand =
new OptionalMemberLookupExpressionTree(
getTreeLocation(start), operand, member, /* isStartOfOptionalChain = */ true);
break;
case NO_SUBSTITUTION_TEMPLATE:
case TEMPLATE_HEAD:
reportError("template literal cannot be used within optional chaining");
break;
default:
if (peekIdOrKeyword()) {
IdentifierToken id = eatIdOrKeywordAsId();
operand =
new OptionalMemberExpressionTree(
getTreeLocation(start), operand, id, /* isStartOfOptionalChain = */ true);
} else {
reportError("syntax error: %s not allowed in optional chain", peekType());
}
}
operand = parseRemainingOptionalChainSegment(operand);
}
return operand;
}
/**
* Parses the remaining components of an optional chain till the current chain's end, or a new
* chain's start.
*
*
`optionalExpression.identifier`, `optionalExpression[expression]`, `optionalExpression(arg1,
* arg2)`, or `optionalExpression?.optionalExpression`
*
*
returns parse tree after trying to parse it as an optional chain
*/
private ParseTree parseRemainingOptionalChainSegment(ParseTree optionalExpression) {
// The optional chain's source info should cover the lhs operand also
SourcePosition start = optionalExpression.location.start;
while (peekOptionalChainSuffix()) {
if (peekType() == TokenType.NO_SUBSTITUTION_TEMPLATE
|| peekType() == TokenType.TEMPLATE_HEAD) {
reportError("template literal cannot be used within optional chaining");
break;
}
switch (peekType()) {
case PERIOD:
eat(TokenType.PERIOD);
IdentifierToken id = eatIdOrKeywordAsId();
optionalExpression =
new OptionalMemberExpressionTree(
getTreeLocation(start),
optionalExpression,
id,
/*isStartOfOptionalChain=*/ false);
break;
case OPEN_PAREN:
ArgumentListTree arguments = parseArguments();
optionalExpression =
new OptionalCallExpressionTree(
getTreeLocation(start),
optionalExpression,
arguments,
/* isStartOfOptionalChain = */ false,
arguments.hasTrailingComma);
break;
case OPEN_SQUARE:
eat(TokenType.OPEN_SQUARE);
ParseTree member = parseExpression();
eat(TokenType.CLOSE_SQUARE);
optionalExpression =
new OptionalMemberLookupExpressionTree(
getTreeLocation(start),
optionalExpression,
member,
/* isStartOfOptionalChain = */ false);
break;
default:
throw new AssertionError("unexpected case: " + peekType());
}
}
return optionalExpression;
}
/** Tokens that indicate continuation of an optional chain. */
private boolean peekOptionalChainSuffix() {
return peek(TokenType.OPEN_PAREN) // a?.b( ...
|| peek(TokenType.OPEN_SQUARE) // a?.b[ ...
|| peek(TokenType.PERIOD) // a?.b. ...
// TEMPLATE_HEAD and NO_SUBSTITUTION_TEMPLATE are actually not allowed within optional
// chaining and leads to an early error as dictated by the spec.
// https://tc39.es/proposal-optional-chaining/#sec-left-hand-side-expressions-static-semantics-early-errors
|| peek(TokenType.NO_SUBSTITUTION_TEMPLATE) // a?.b`text`
|| peek(TokenType.TEMPLATE_HEAD); // a?.b`text ${substitution} text`
}
private static final String ASYNC = "async";
// 11.2 Member Expression without the new production
private ParseTree parseMemberExpressionNoNew() {
SourcePosition start = getTreeStartLocation();
ParseTree operand;
if (peekImportDot()) {
operand = parseImportDotMeta();
} else if (peekAsyncFunctionStart()) {
operand = parseAsyncFunctionExpression();
} else if (peekFunction()) {
operand = parseFunctionExpression();
} else {
operand = parsePrimaryExpression();
}
while (peekMemberExpressionSuffix()) {
switch (peekType()) {
case OPEN_SQUARE:
eat(TokenType.OPEN_SQUARE);
ParseTree member = parseExpression();
eat(TokenType.CLOSE_SQUARE);
operand = new MemberLookupExpressionTree(
getTreeLocation(start), operand, member);
break;
case PERIOD:
eat(TokenType.PERIOD);
IdentifierToken id = eatIdOrKeywordAsId();
operand = new MemberExpressionTree(getTreeLocation(start), operand, id);
break;
case NO_SUBSTITUTION_TEMPLATE:
case TEMPLATE_HEAD:
operand = parseTemplateLiteral(operand);
break;
default:
throw new RuntimeException("unreachable");
}
}
return operand;
}
private boolean peekMemberExpressionSuffix() {
return peek(TokenType.OPEN_SQUARE) || peek(TokenType.PERIOD)
|| peek(TokenType.NO_SUBSTITUTION_TEMPLATE)
|| peek(TokenType.TEMPLATE_HEAD);
}
private ParseTree parseNewExpression() {
if (!peek(TokenType.NEW)) {
return parseMemberExpressionNoNew();
} else if (peek(1, TokenType.PERIOD)) {
return parseNewDotSomething();
} else {
SourcePosition start = getTreeStartLocation();
eat(TokenType.NEW);
if (peek(TokenType.QUESTION_DOT)) { // new?.target not allowed
reportError("Optional chaining is forbidden in `new?.target` contexts.");
}
ParseTree operand = parseNewExpression();
if (peek(TokenType.QUESTION_DOT)) { // new a?.() not allowed
reportError("Optional chaining is forbidden in construction contexts.");
}
ArgumentListTree arguments = null;
if (peek(TokenType.OPEN_PAREN)) {
arguments = parseArguments();
}
return new NewExpressionTree(
getTreeLocation(start),
operand,
arguments,
arguments != null && arguments.hasTrailingComma);
}
}
private ParseTree parseNewDotSomething() {
// currently only "target" is valid after "new."
SourcePosition start = getTreeStartLocation();
eat(TokenType.NEW);
eat(TokenType.PERIOD);
eatPredefinedString("target");
return new NewTargetExpressionTree(getTreeLocation(start));
}
private ParseTree parseImportDotMeta() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.IMPORT);
eat(TokenType.PERIOD);
eatPredefinedString("meta");
return new ImportMetaExpressionTree(getTreeLocation(start));
}
private ArgumentListTree parseArguments() {
// ArgumentList :
// AssignmentOrSpreadExpression
// ArgumentList , AssignmentOrSpreadExpression
//
// AssignmentOrSpreadExpression :
// ... AssignmentExpression
// AssignmentExpression
SourcePosition start = getTreeStartLocation();
ImmutableList.Builder arguments = ImmutableList.builder();
boolean trailingComma = false;
ImmutableList.Builder commaPositions = ImmutableList.builder();
eat(TokenType.OPEN_PAREN);
while (peekAssignmentOrSpread()) {
arguments.add(parseAssignmentOrSpread());
if (!peek(TokenType.CLOSE_PAREN)) {
Token comma = eat(TokenType.COMMA);
if (comma != null) {
commaPositions.add(comma.getStart());
}
if (peek(TokenType.CLOSE_PAREN)) {
recordFeatureUsed(Feature.TRAILING_COMMA_IN_PARAM_LIST);
if (!config.atLeast8) {
reportError(comma, "Invalid trailing comma in arguments list");
}
trailingComma = true;
}
}
}
eat(TokenType.CLOSE_PAREN);
return new ArgumentListTree(
getTreeLocation(start), arguments.build(), trailingComma, commaPositions.build());
}
/**
* Whether we have a spread expression or an assignment next.
*
* This does not peek the operand for the spread expression. This means that
* {@link #parseAssignmentOrSpread} might still fail when this returns true.
*/
private boolean peekAssignmentOrSpread() {
return peek(TokenType.ELLIPSIS) || peekAssignmentExpression();
}
private ParseTree parseAssignmentOrSpread() {
if (peek(TokenType.ELLIPSIS)) {
return parseIterSpread();
}
return parseAssignmentExpression();
}
// Destructuring (aka pattern matching); see
// http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
// Kinds of destructuring patterns
private enum PatternKind {
// A var, let, const; catch head; or formal parameter list--only
// identifiers are allowed as lvalues
INITIALIZER,
// An assignment or for-in initializer--any lvalue is allowed
ANY,
}
private boolean peekPatternStart() {
return peek(TokenType.OPEN_SQUARE) || peek(TokenType.OPEN_CURLY);
}
private ParseTree parsePattern(PatternKind kind) {
switch (peekType()) {
case OPEN_SQUARE:
return parseArrayPattern(kind);
case OPEN_CURLY:
default:
return parseObjectPattern(kind);
}
}
private boolean peekArrayPatternElement() {
return peekExpression();
}
private ParseTree parseIterRest(PatternKind patternKind) {
SourcePosition start = getTreeStartLocation();
eat(TokenType.ELLIPSIS);
ParseTree patternAssignmentTarget = parseRestAssignmentTarget(patternKind);
return new IterRestTree(getTreeLocation(start), patternAssignmentTarget);
}
private ParseTree parseRestAssignmentTarget(PatternKind patternKind) {
ParseTree patternAssignmentTarget = parsePatternAssignmentTargetNoDefault(patternKind);
if (peek(TokenType.EQUAL)) {
reportError("A default value cannot be specified after '...'");
}
return patternAssignmentTarget;
}
// Pattern ::= ... | "[" Element? ("," Element?)* "]"
private ParseTree parseArrayPattern(PatternKind kind) {
SourcePosition start = getTreeStartLocation();
ImmutableList.Builder elements = ImmutableList.builder();
eat(TokenType.OPEN_SQUARE);
while (peek(TokenType.COMMA) || peekArrayPatternElement()) {
if (peek(TokenType.COMMA)) {
SourcePosition nullStart = getTreeStartLocation();
eat(TokenType.COMMA);
elements.add(new NullTree(getTreeLocation(nullStart)));
} else {
elements.add(parsePatternAssignmentTarget(kind));
if (peek(TokenType.COMMA)) {
// Consume the comma separator
eat(TokenType.COMMA);
} else {
// Otherwise we must be done
break;
}
}
}
if (peek(TokenType.ELLIPSIS)) {
recordFeatureUsed(Feature.ARRAY_PATTERN_REST);
elements.add(parseIterRest(kind));
}
if (eat(TokenType.CLOSE_SQUARE) == null) {
// If we get no closing bracket then return invalid tree to avoid compiler tripping
// downstream. It's needed only for IDE mode where compiler continues processing even if
// source has syntactic errors.
return new MissingPrimaryExpressionTree(getTreeLocation(getTreeStartLocation()));
}
return new ArrayPatternTree(getTreeLocation(start), elements.build());
}
// Pattern ::= "{" (Field ("," Field)* ","?)? "}" | ...
private ParseTree parseObjectPattern(PatternKind kind) {
SourcePosition start = getTreeStartLocation();
ImmutableList.Builder fields = ImmutableList.builder();
eat(TokenType.OPEN_CURLY);
while (peekObjectPatternField()) {
fields.add(parseObjectPatternField(kind));
if (peek(TokenType.COMMA)) {
// Consume the comma separator
eat(TokenType.COMMA);
} else {
// Otherwise we must be done
break;
}
}
if (peek(TokenType.ELLIPSIS)) {
recordFeatureUsed(Feature.OBJECT_PATTERN_REST);
SourcePosition restStart = getTreeStartLocation();
eat(TokenType.ELLIPSIS);
ParseTree patternAssignmentTarget = parseRestAssignmentTarget(kind);
fields.add(new ObjectRestTree(getTreeLocation(restStart), patternAssignmentTarget));
}
eat(TokenType.CLOSE_CURLY);
return new ObjectPatternTree(getTreeLocation(start), fields.build());
}
private boolean peekObjectPatternField() {
return peekPropertyNameOrComputedProp(0);
}
private ParseTree parseObjectPatternField(PatternKind kind) {
SourcePosition start = getTreeStartLocation();
if (peekType() == TokenType.OPEN_SQUARE) {
ParseTree key = parseComputedPropertyName();
eat(TokenType.COLON);
ParseTree value = parsePatternAssignmentTarget(kind);
return new ComputedPropertyDefinitionTree(getTreeLocation(start), key, value);
}
Token name;
if (peekIdOrKeyword()) {
name = eatIdOrKeywordAsId();
if (!peek(TokenType.COLON)) {
IdentifierToken idToken = (IdentifierToken) name;
if (Keywords.isKeyword(idToken.value, /* includeTypeScriptKeywords = */ false)) {
reportError("cannot use keyword '%s' here.", name);
}
if (peek(TokenType.EQUAL)) {
IdentifierExpressionTree idTree = new IdentifierExpressionTree(
getTreeLocation(start), idToken);
eat(TokenType.EQUAL);
ParseTree defaultValue = parseAssignmentExpression();
return new DefaultParameterTree(getTreeLocation(start), idTree, defaultValue);
}
return new PropertyNameAssignmentTree(getTreeLocation(start), name, null);
}
} else {
name = parseLiteralExpression().literalToken;
}
eat(TokenType.COLON);
ParseTree value = parsePatternAssignmentTarget(kind);
return new PropertyNameAssignmentTree(getTreeLocation(start), name, value);
}
/**
* A PatternAssignmentTarget is the location where the assigned value gets stored, including an
* optional default value.
*
*
* - Spec AssignmentElement === PatternAssignmentTarget(PatternKind.ANY)
*
- Valid in an assignment that is not a formal parameter list or variable declaration.
* Sub-patterns and arbitrary left hand side expressions are allowed.
*
*
- Spec BindingElement === PatternAssignmentElement(PatternKind.INITIALIZER)
*
- Valid in a formal parameter list or variable declaration statement.
* Only sub-patterns and identifiers are allowed.
*
* Examples:
*
*
* [a, {foo: b = 'default'}] = someArray; // valid
* [x.a, {foo: x.b = 'default'}] = someArray; // valid
*
* let [a, {foo: b = 'default'}] = someArray; // valid
* let [x.a, {foo: x.b = 'default'}] = someArray; // invalid
*
* function f([a, {foo: b = 'default'}]) {...} // valid
* function f([x.a, {foo: x.b = 'default'}]) {...} // invalid
*
*
*/
private ParseTree parsePatternAssignmentTarget(PatternKind patternKind) {
SourcePosition start = getTreeStartLocation();
ParseTree assignmentTarget;
assignmentTarget = parsePatternAssignmentTargetNoDefault(patternKind);
if (peek(TokenType.EQUAL)) {
eat(TokenType.EQUAL);
ParseTree defaultValue = parseAssignmentExpression();
assignmentTarget =
new DefaultParameterTree(getTreeLocation(start), assignmentTarget, defaultValue);
}
return assignmentTarget;
}
private ParseTree parsePatternAssignmentTargetNoDefault(PatternKind kind) {
ParseTree assignmentTarget;
if (peekPatternStart()) {
assignmentTarget = parsePattern(kind);
} else {
assignmentTarget = parseLeftHandSideExpression();
if (!assignmentTarget.isValidAssignmentTarget()) {
reportError("invalid assignment target");
}
if (kind == PatternKind.INITIALIZER
&& assignmentTarget.type != ParseTreeType.IDENTIFIER_EXPRESSION) {
// We're in the context of a formal parameter list or a variable declaration statement
reportError("Only an identifier or destructuring pattern is allowed here.");
}
}
return assignmentTarget;
}
private ParseTree parseTypeAlias() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.TYPE);
IdentifierToken alias = eatId();
eat(TokenType.EQUAL);
ParseTree original = parseType();
eatPossiblyImplicitSemiColon();
return new TypeAliasTree(getTreeLocation(start), alias, original);
}
private ParseTree parseAmbientDeclaration() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.DECLARE);
ParseTree declare = parseAmbientDeclarationHelper();
return new AmbientDeclarationTree(getTreeLocation(start), declare);
}
private ParseTree parseAmbientDeclarationHelper() {
ParseTree declare;
switch (peekType()) {
case FUNCTION:
declare = parseAmbientFunctionDeclaration();
eatPossiblyImplicitSemiColon();
break;
case CLASS:
declare = parseClassDeclaration(true);
break;
case ENUM:
declare = parseEnumDeclaration();
break;
case MODULE:
case NAMESPACE:
declare = parseNamespaceDeclaration(true);
break;
case VAR:
case LET:
case CONST:
default: // unreachable, parse as a var decl to get a parse error.
declare = parseAmbientVariableDeclarationList();
eatPossiblyImplicitSemiColon();
break;
}
return declare;
}
/** Consume a (possibly implicit) semi-colon. Reports an error if a semi-colon is not present. */
private void eatPossiblyImplicitSemiColon() {
if (peek(TokenType.SEMI_COLON)) {
eat(TokenType.SEMI_COLON);
return;
}
if (peekImplicitSemiColon()) {
return;
}
reportError("Semi-colon expected");
}
/**
* Returns true if an implicit or explicit semi colon is at the current location.
*/
private boolean peekImplicitSemiColon() {
return peekImplicitSemiColon(0);
}
private boolean peekImplicitSemiColon(int index) {
boolean lineAdvanced;
if (index == 0) {
lineAdvanced = getNextLine() > getLastLine();
} else {
lineAdvanced =
peekToken(index).location.start.line > peekToken(index - 1).location.end.line;
}
return lineAdvanced
|| peek(index, TokenType.SEMI_COLON)
|| peek(index, TokenType.CLOSE_CURLY)
|| peek(index, TokenType.END_OF_FILE);
}
/**
* Returns the line number of the most recently consumed token.
*/
private int getLastLine() {
return lastSourcePosition.line;
}
/**
* Returns the line number of the next token.
*/
private int getNextLine() {
return peekToken().location.start.line;
}
/**
* Consumes the next token if it is of the expected type. Otherwise returns null.
* Never reports errors.
*
* @param expectedTokenType
* @return The consumed token, or null if the next token is not of the expected type.
*/
private Token eatOpt(TokenType expectedTokenType) {
if (peek(expectedTokenType)) {
return eat(expectedTokenType);
}
return null;
}
private boolean inStrictContext() {
// TODO(johnlenz): track entering strict scripts/modules/functions.
return config.isStrictMode;
}
private boolean peekId() {
return peekId(0);
}
/** @return whether the next token is an identifier. */
private boolean peekId(int index) {
TokenType type = peekType(index);
// There are two special cases to handle here:
// * outside of strict-mode code strict-mode keywords can be used as identifiers
// * when configured to parse TypeScript code the scanner will return TypeScript
// keyword token but these contextual keywords can always be used as idenifiers.
return TokenType.IDENTIFIER == type
|| (config.parseTypeSyntax && Keywords.isTypeScriptSpecificKeyword(type))
|| (!inStrictContext() && Keywords.isStrictKeyword(type));
}
private boolean peekIdOrKeyword() {
return peekIdOrKeyword(0);
}
private boolean peekIdOrKeyword(int index) {
TokenType type = peekType(index);
return TokenType.IDENTIFIER == type || Keywords.isKeyword(type);
}
private boolean peekAccessibilityModifier() {
return EnumSet.of(TokenType.PUBLIC, TokenType.PROTECTED, TokenType.PRIVATE)
.contains(peekType());
}
private TokenType maybeParseAccessibilityModifier() {
if (config.parseTypeSyntax && peekAccessibilityModifier()) {
return nextToken().type;
} else {
return null;
}
}
/**
* Shorthand for eatOpt(TokenType.IDENTIFIER)
*/
private IdentifierToken eatIdOpt() {
return (peekId()) ? eatIdOrKeywordAsId() : null;
}
/**
* Consumes an identifier token that is not a reserved word.
* @see "http://www.ecma-international.org/ecma-262/5.1/#sec-7.6"
*/
private IdentifierToken eatId() {
if (peekId()) {
return eatIdOrKeywordAsId();
} else {
reportExpectedError(peekToken(), TokenType.IDENTIFIER);
if (peekIdOrKeyword()) {
return eatIdOrKeywordAsId();
} else {
return null;
}
}
}
private Token eatObjectLiteralPropertyName() {
Token token = peekToken();
switch (token.type) {
case STRING:
case NUMBER:
case BIGINT:
return nextToken();
case IDENTIFIER:
default:
return eatIdOrKeywordAsId();
}
}
/**
* Consumes an identifier token that may be a reserved word, i.e.
* an IdentifierName, not necessarily an Identifier.
* @see "http://www.ecma-international.org/ecma-262/5.1/#sec-7.6"
*/
private IdentifierToken eatIdOrKeywordAsId() {
Token token = nextToken();
if (token.type == TokenType.IDENTIFIER) {
return (IdentifierToken) token;
} else if (Keywords.isKeyword(token.type)) {
return new IdentifierToken(
token.location, Keywords.get(token.type).toString());
} else {
reportExpectedError(token, TokenType.IDENTIFIER);
}
return null;
}
/**
* Consumes the next token. If the consumed token is not of the expected type then
* report an error and return null. Otherwise return the consumed token.
*
* @param expectedTokenType
* @return The consumed token, or null if the next token is not of the expected type.
*/
private Token eat(TokenType expectedTokenType) {
Token token = nextToken();
if (token.type != expectedTokenType) {
reportExpectedError(token, expectedTokenType);
return null;
}
return token;
}
/**
* Report a 'X' expected error message.
* @param token The location to report the message at.
* @param expected The thing that was expected.
*/
private void reportExpectedError(Token token, Object expected) {
reportError(token, "'%s' expected", expected);
}
/**
* Returns a SourcePosition for the start of a parse tree that starts at the current location.
*/
private SourcePosition getTreeStartLocation() {
return peekToken().location.start;
}
/**
* Returns a SourcePosition for the end of a parse tree that ends at the current location.
*/
private SourcePosition getTreeEndLocation() {
return lastSourcePosition;
}
/**
* Returns a SourceRange for a parse tree that starts at {start} and ends at the current
* location.
*/
private SourceRange getTreeLocation(SourcePosition start) {
return new SourceRange(start, getTreeEndLocation());
}
/**
* Consumes the next token and returns it. Will return a never ending stream of
* TokenType.END_OF_FILE at the end of the file so callers don't have to check for EOF
* explicitly.
*
* Tokenizing is contextual. nextToken() will never return a regular expression literal.
*/
private Token nextToken() {
Token token = scanner.nextToken();
lastSourcePosition = token.location.end;
return token;
}
/**
* Consumes a regular expression literal token and returns it.
*/
private LiteralToken nextRegularExpressionLiteralToken() {
LiteralToken token = scanner.nextRegularExpressionLiteralToken();
lastSourcePosition = token.location.end;
return token;
}
/** Consumes a template literal token and returns it. */
private TemplateLiteralToken nextTemplateLiteralToken() {
TemplateLiteralToken token = scanner.nextTemplateLiteralToken();
lastSourcePosition = token.location.end;
return token;
}
/**
* Returns true if the next token is of the expected type. Does not consume the token.
*/
private boolean peek(TokenType expectedType) {
return peek(0, expectedType);
}
/**
* Returns true if the index-th next token is of the expected type. Does not consume
* any tokens.
*/
private boolean peek(int index, TokenType expectedType) {
return peekType(index) == expectedType;
}
/**
* Returns the TokenType of the next token. Does not consume any tokens.
*/
private TokenType peekType() {
return peekType(0);
}
/**
* Returns the TokenType of the index-th next token. Does not consume any tokens.
*/
private TokenType peekType(int index) {
return peekToken(index).type;
}
/**
* Returns the next token. Does not consume any tokens.
*/
private Token peekToken() {
return peekToken(0);
}
/**
* Returns the index-th next token. Does not consume any tokens.
*/
private Token peekToken(int index) {
return scanner.peekToken(index);
}
/**
* Forks the parser at the current point and returns a new
* parser for speculative parsing.
*
* @deprecated Creating a lookahead parser often leads to exponential parse times
* (see issues #1049, #1115, and #1148 on github) so avoid using this if possible.
*/
@Deprecated
private Parser createLookaheadParser() {
return new Parser(
config,
new LookaheadErrorReporter(),
this.scanner.getFile(),
this.scanner.getOffset(),
inGeneratorContext());
}
/**
* Reports an error message at a given token.
* @param token The location to report the message at.
* @param message The message to report in String.format style.
* @param arguments The arguments to fill in the message format.
*/
@FormatMethod
private void reportError(Token token, @FormatString String message, Object... arguments) {
if (token == null) {
reportError(message, arguments);
} else {
errorReporter.reportError(token.getStart(), message, arguments);
}
}
/**
* Reports an error message at a given parse tree's location.
*
* @param parseTree The location to report the message at.
* @param message The message to report in String.format style.
* @param arguments The arguments to fill in the message format.
*/
@FormatMethod
private void reportError(ParseTree parseTree, @FormatString String message, Object... arguments) {
if (parseTree == null) {
reportError(message, arguments);
} else {
errorReporter.reportError(parseTree.location.start, message, arguments);
}
}
/**
* Reports an error at the current location.
* @param message The message to report in String.format style.
* @param arguments The arguments to fill in the message format.
*/
@FormatMethod
private void reportError(@FormatString String message, Object... arguments) {
errorReporter.reportError(scanner.getPosition(), message, arguments);
}
/**
* Reports an error at the specified location.
*
* @param position The position of the error.
* @param message The message to report in String.format style.
* @param arguments The arguments to fill in the message format.
*/
@FormatMethod
private void reportError(
SourcePosition position, @FormatString String message, Object... arguments) {
errorReporter.reportError(position, message, arguments);
}
private void reportTemplateErrorIfPresent(TemplateLiteralToken templateToken) {
if (templateToken.errorMessage != null) {
reportError(templateToken.errorPosition, "%s", templateToken.errorMessage);
}
}
private Parser recordFeatureUsed(Feature feature) {
features = features.with(feature);
return this;
}
}