traceur
Version:
ES6 to ES5 compiler
1,836 lines (1,671 loc) • 131 kB
JavaScript
// Copyright 2012 Traceur 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.
import {FindVisitor} from '../codegeneration/FindVisitor.js';
import {IdentifierToken} from './IdentifierToken.js';
import {
ARRAY_LITERAL,
BINDING_IDENTIFIER,
CALL_EXPRESSION,
COMPUTED_PROPERTY_NAME,
COVER_FORMALS,
FORMAL_PARAMETER_LIST,
IDENTIFIER_EXPRESSION,
LITERAL_PROPERTY_NAME,
OBJECT_LITERAL,
REST_PARAMETER,
SYNTAX_ERROR_TREE
} from './trees/ParseTreeType.js';
import {Options} from '../Options.js';
import {
AS,
ASYNC,
ASYNC_STAR,
AWAIT,
CONSTRUCTOR,
FROM,
GET,
OF,
ON,
SET,
TYPE,
} from './PredefinedName.js';
import {SyntaxErrorReporter} from '../util/SyntaxErrorReporter.js';
import {
getLastToken,
getPosition,
init as initScanner,
isAtEnd,
nextCloseAngle,
nextJsxTextToken,
nextJsxToken,
nextRegularExpressionLiteralToken,
nextTemplateLiteralToken,
nextToken,
peek,
peekJsxToken,
peekLocation,
peekLookahead,
peekToken,
peekTokenLookahead,
peekTokenNoLineTerminator,
peekType,
setIndex as resetScanner,
} from './Scanner.js';
import {SourceRange} from '../util/SourceRange.js';
import {
Token,
isAssignmentOperator
} from './Token.js';
import {getKeywordType} from './Keywords.js';
import {validateConstructor} from '../semantics/ConstructorValidator.js';
import validateParameters from '../staticsemantics/validateParameters.js';
import isValidSimpleAssignmentTarget from '../staticsemantics/isValidSimpleAssignmentTarget.js';
import {
AMPERSAND,
AND,
ARROW,
AT,
BANG,
BAR,
BREAK,
CARET,
CASE,
CATCH,
CLASS,
CLOSE_ANGLE,
CLOSE_CURLY,
CLOSE_PAREN,
CLOSE_SQUARE,
COLON,
COMMA,
CONST,
CONTINUE,
DEBUGGER,
DEFAULT,
DELETE,
DO,
DOT_DOT_DOT,
ELSE,
END_OF_FILE,
EQUAL,
EQUAL_EQUAL,
EQUAL_EQUAL_EQUAL,
ERROR,
EXPORT,
EXTENDS,
FALSE,
FINALLY,
FOR,
FUNCTION,
GREATER_EQUAL,
IDENTIFIER,
IF,
IMPLEMENTS,
IMPORT,
IN,
INSTANCEOF,
INTERFACE,
JSX_IDENTIFIER,
LEFT_SHIFT,
LESS_EQUAL,
LET,
MINUS,
MINUS_MINUS,
NEW,
NO_SUBSTITUTION_TEMPLATE,
NOT_EQUAL,
NOT_EQUAL_EQUAL,
NULL,
NUMBER,
OPEN_ANGLE,
OPEN_CURLY,
OPEN_PAREN,
OPEN_SQUARE,
OR,
PACKAGE,
PERCENT,
PERIOD,
PLUS,
PLUS_PLUS,
PRIVATE,
PROTECTED,
PUBLIC,
QUESTION,
RETURN,
RIGHT_SHIFT,
SEMI_COLON,
SLASH,
SLASH_EQUAL,
STAR,
STAR_STAR,
STATIC,
STRING,
SUPER,
SWITCH,
TEMPLATE_HEAD,
TEMPLATE_TAIL,
THIS,
THROW,
TILDE,
TRUE,
TRY,
TYPEOF,
UNSIGNED_RIGHT_SHIFT,
VAR,
VOID,
WHILE,
WITH,
YIELD
} from './TokenType.js';
import {
ArgumentList,
ArrayComprehension,
ArrayLiteral,
ArrayPattern,
ArrayType,
ArrowFunction,
AssignmentElement,
AwaitExpression,
BinaryExpression,
BindingElement,
BindingIdentifier,
Block,
BreakStatement,
CallExpression,
CallSignature,
CaseClause,
Catch,
ClassDeclaration,
ClassExpression,
CommaExpression,
ComprehensionFor,
ComprehensionIf,
ComputedPropertyName,
ConditionalExpression,
ConstructSignature,
ConstructorType,
ContinueStatement,
CoverFormals,
CoverInitializedName,
DebuggerStatement,
Annotation,
DefaultClause,
DoWhileStatement,
EmptyStatement,
ExportDeclaration,
ExportDefault,
ExportSpecifier,
ExportSpecifierSet,
ExportStar,
ExpressionStatement,
Finally,
ForInStatement,
ForOfStatement,
ForOnStatement,
ForStatement,
FormalParameter,
FormalParameterList,
ForwardDefaultExport,
FunctionBody,
FunctionDeclaration,
FunctionExpression,
FunctionType,
GeneratorComprehension,
GetAccessor,
IdentifierExpression,
IfStatement,
ImportClausePair,
ImportDeclaration,
ImportSpecifier,
ImportSpecifierSet,
ImportedBinding,
ImportTypeClause,
IndexSignature,
InterfaceDeclaration,
JsxAttribute,
JsxElement,
JsxElementName,
JsxPlaceholder,
JsxSpreadAttribute,
JsxText,
LabelledStatement,
LiteralExpression,
LiteralPropertyName,
MemberExpression,
MemberLookupExpression,
Method,
MethodSignature,
Module,
ModuleSpecifier,
NameSpaceExport,
NameSpaceImport,
NamedExport,
NewExpression,
ObjectLiteral,
ObjectPattern,
ObjectPatternField,
ObjectType,
ParenExpression,
PostfixExpression,
PredefinedType,
PropertyNameAssignment,
PropertyNameShorthand,
PropertySignature,
PropertyVariableDeclaration,
RestParameter,
ReturnStatement,
Script,
SetAccessor,
SpreadExpression,
SpreadPatternElement,
SuperExpression,
SwitchStatement,
SyntaxErrorTree,
TemplateLiteralExpression,
TemplateLiteralPortion,
TemplateSubstitution,
ThisExpression,
ThrowStatement,
TryStatement,
TypeAliasDeclaration,
TypeArguments,
TypeName,
TypeParameter,
TypeParameters,
TypeReference,
UnaryExpression,
UnionType,
VariableDeclaration,
VariableDeclarationList,
VariableStatement,
WhileStatement,
WithStatement,
YieldExpression
} from './trees/ParseTrees.js';
/**
* Differentiates between parsing for 'In' vs. 'NoIn'
* Variants of expression grammars.
*/
const ALLOW_IN = true;
const NO_IN = false;
/**
* Enum for determining if the initializer is needed in a variable declaration
* with a destructuring pattern.
*/
const INITIALIZER_REQUIRED = true;
const INITIALIZER_OPTIONAL = false;
/**
* Used to find invalid CoverInitializedName trees. This is used when we know
* the tree is not going to be used as a pattern.
*/
class ValidateObjectLiteral extends FindVisitor {
constructor() {
super();
this.errorToken = null;
}
visitCoverInitializedName(tree) {
this.errorToken = tree.equalToken;
this.found = true;
}
}
/**
* @param {Array.<VariableDeclaration>} declarations
* @return {boolean}
*/
function containsInitializer(declarations) {
return declarations.some((v) => v.initializer);
}
const FUNCTION_STATE_SCRIPT = 1;
const FUNCTION_STATE_MODULE = 1 << 1;
const FUNCTION_STATE_FUNCTION = 1 << 2;
const FUNCTION_STATE_ARROW = 1 << 3;
const FUNCTION_STATE_METHOD = 1 << 4;
const FUNCTION_STATE_DERIVED_CONSTRUCTOR = 1 << 5;
const FUNCTION_STATE_GENERATOR = 1 << 6;
const FUNCTION_STATE_ASYNC = 1 << 7;
const FUNCTION_STATE_LENIENT =
FUNCTION_STATE_METHOD | FUNCTION_STATE_GENERATOR |
FUNCTION_STATE_ASYNC | FUNCTION_STATE_DERIVED_CONSTRUCTOR;
/**
* This is used to track the functions as the parser descends. It allows
* us to determine if we are in a function and what kind of function it is.
* This is used to determine if `return` and `super` are allowed.
*/
class FunctionState {
constructor(outer, kind) {
this.outer = outer;
this.kind = kind;
}
isTopMost() {
return this.kind & (FUNCTION_STATE_SCRIPT | FUNCTION_STATE_MODULE);
}
isMethod() {
return this.kind & FUNCTION_STATE_METHOD;
}
isDerivedConstructor() {
return this.kind & FUNCTION_STATE_DERIVED_CONSTRUCTOR;
}
isArrowFunction() {
return this.kind & FUNCTION_STATE_ARROW;
}
isGenerator() {
return this.kind & FUNCTION_STATE_GENERATOR;
}
isAsyncFunction() {
return this.kind & FUNCTION_STATE_ASYNC;
}
isAsyncGenerator() {
return this.isGenerator() && this.isAsyncFunction();
}
}
/**
* Parses a javascript file.
*
* The various this.parseX_() methods never return null - even when parse errors
* are encountered.Typically this.parseX_() will return a XTree ParseTree. Each
* ParseTree that is created includes its source location. The typical pattern
* for a this.parseX_() method is:
*
* XTree this.parseX_() {
* let start = this.getTreeStartLocation_();
* parse X grammar element and its children
* return new XTree(this.getTreeLocation_(start), children);
* }
*
* this.parseX_() methods must consume at least 1 token - even in error cases.
* This prevents infinite loops in the parser.
*
* Many this.parseX_() methods are matched by a 'boolean this.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 this.eat_() method consumes a token and reports an error if the consumed
* token is not of the expected type. The this.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:
* this.eat_(LIST_START);
* let elements = [];
* while (this.peekListElement_()) {
* elements.push(this.parseListElement_());
* }
* this.eat_(LIST_END);
*/
export class Parser {
/**
* @param {SourceFile} file
* @param {ErrorReporter} errorReporter
* @param {Options} options
*/
constructor(file, errorReporter = new SyntaxErrorReporter(),
options = new Options()) {
this.errorReporter_ = errorReporter;
initScanner(errorReporter, file, this, options);
this.options_ = options;
// This is used in conjunction with ensureNoCoverInitializedNames_ to
// determine if there has been any added CoverInitializedName since last
// time this was read.
this.coverInitializedNameCount_ = 0;
/**
* Keeps track of whether we are currently in strict mode parsing or not.
*/
this.strictMode_ = false;
this.annotations_ = [];
// TODO(arv): Use function state to track strict mode.
this.functionState_ = null;
}
get allowYield_() {
return this.functionState_.isGenerator();
}
get allowAwait_() {
return this.functionState_.isAsyncFunction();
}
get allowForOn_() {
return this.functionState_.isAsyncFunction();
}
// 14 Script
/**
* @return {Script}
*/
parseScript() {
this.strictMode_ = false;
let start = this.getTreeStartLocation_();
let fs = this.pushFunctionState_(FUNCTION_STATE_SCRIPT);
let scriptItemList = this.parseStatementList_(true);
this.eat_(END_OF_FILE);
this.popFunctionState_(fs);
return new Script(this.getTreeLocation_(start), scriptItemList, null);
}
pushFunctionState_(kind) {
return this.functionState_ = new FunctionState(this.functionState_, kind);
}
popFunctionState_(fs) {
if (fs != this.functionState_) {
throw new Error('Internal error');
}
this.functionState_ = this.functionState_.outer;
}
// StatementList :
// StatementListItem
// StatementList StatementListItem
/**
* @return {Array.<ParseTree>}
* @private
*/
parseStatementList_(checkUseStrictDirective) {
let result = [];
let type;
// We do a lot of type assignment in loops like these for performance
// reasons.
while ((type = peekType()) !== CLOSE_CURLY && type !== END_OF_FILE) {
let statement = this.parseStatementListItem_(type);
if (checkUseStrictDirective) {
if (!statement.isDirectivePrologue()) {
checkUseStrictDirective = false;
} else if (statement.isUseStrictDirective()) {
this.strictMode_ = true;
checkUseStrictDirective = false;
}
}
result.push(statement);
}
return result;
}
// ScriptItem :
// ImportDeclaration
// StatementListItem
/**
* @return {ParseTree}
* @private
*/
parseStatementListItem_(type) {
// Declaration
switch (type) {
case LET:
case CONST:
if (this.options_.blockBinding) {
return this.parseVariableStatement_();
}
break;
case CLASS:
if (this.options_.classes) {
return this.parseClassDeclaration_();
}
break;
case FUNCTION:
return this.parseFunctionDeclaration_();
case IDENTIFIER:
if (this.options_.types && this.peekPredefinedString_(TYPE) &&
peekLookahead(IDENTIFIER)) {
return this.parseTypeAliasDeclaration_();
}
break;
}
// Statement
return this.parseStatementWithType_(type);
}
parseModule() {
let start = this.getTreeStartLocation_();
let fs = this.pushFunctionState_(FUNCTION_STATE_MODULE);
let scriptItemList = this.parseModuleItemList_();
this.eat_(END_OF_FILE);
this.popFunctionState_(fs);
return new Module(this.getTreeLocation_(start), scriptItemList, null);
}
parseModuleItemList_() {
this.strictMode_ = true;
let result = [];
let type;
while ((type = peekType()) !== END_OF_FILE) {
let statement = this.parseModuleItem_(type);
result.push(statement);
}
return result;
}
parseModuleItem_(type) {
switch (type) {
case IMPORT:
return this.parseImportDeclaration_();
case EXPORT:
return this.parseExportDeclaration_();
case AT:
if (this.options_.annotations)
return this.parseAnnotatedDeclarations_(true);
break;
}
return this.parseStatementListItem_(type);
}
parseModuleSpecifier_() {
// ModuleSpecifier :
// StringLiteral
let start = this.getTreeStartLocation_();
let token = this.eat_(STRING);
return new ModuleSpecifier(this.getTreeLocation_(start), token);
}
// ClassDeclaration
// ImportDeclaration
// ExportDeclaration
// Statement (other than BlockStatement)
// FunctionDeclaration
// ImportDeclaration ::= "import" ImportDeclaration
/**
* @return {NameSpaceImport}
*/
parseNameSpaceImport_() {
let start = this.getTreeStartLocation_();
this.eat_(STAR);
this.eatId_(AS);
let binding = this.parseImportedBinding_();
return new NameSpaceImport(this.getTreeLocation_(start), binding);
}
/**
* @return {ParseTree}
* @private
*/
parseImportDeclaration_() {
let start = this.getTreeStartLocation_();
this.eat_(IMPORT);
let importClause = null;
if (!peek(STRING)) {
importClause = this.parseImportClause_(true, this.options_.types);
this.eatId_(FROM);
}
let moduleSpecifier = this.parseModuleSpecifier_();
this.eatPossibleImplicitSemiColon_();
return new ImportDeclaration(this.getTreeLocation_(start),
importClause, moduleSpecifier);
}
parseImportClause_(allowImportedDefaultBinding, allowType) {
switch (peekType()) {
case STAR:
return this.parseNameSpaceImport_();
case OPEN_CURLY:
return this.parseImportSpecifierSet_();
case IDENTIFIER:
if (allowType && this.peekPredefinedString_(TYPE)) {
let start = this.getTreeStartLocation_();
let t = peekTokenLookahead();
if (t.type === OPEN_CURLY ||
t.type === IDENTIFIER && t.value !== FROM) {
this.eatId_(TYPE);
let clause =
this.parseImportClause_(allowImportedDefaultBinding, false);
return new ImportTypeClause(this.getTreeLocation_(start), clause);
}
}
if (allowImportedDefaultBinding) {
let start = this.getTreeStartLocation_();
let importedBinding = this.parseImportedBinding_();
if (this.eatIf_(COMMA)) {
let second = this.parseImportClause_(false, false);
return new ImportClausePair(this.getTreeLocation_(start),
importedBinding, second);
}
return importedBinding;
}
break;
}
return this.parseUnexpectedToken_();
}
// https://bugs.ecmascript.org/show_bug.cgi?id=2287
// ImportClause :
// ImportedBinding
// NamedImports
parseImportSpecifierSet_() {
let start = this.getTreeStartLocation_();
let specifiers = [];
this.eat_(OPEN_CURLY);
while (!peek(CLOSE_CURLY) && !isAtEnd()) {
specifiers.push(this.parseImportSpecifier_());
if (!this.eatIf_(COMMA))
break;
}
this.eat_(CLOSE_CURLY);
return new ImportSpecifierSet(this.getTreeLocation_(start), specifiers);
}
parseImportedBinding_() {
let start = this.getTreeStartLocation_();
let binding = this.parseBindingIdentifier_();
return new ImportedBinding(this.getTreeLocation_(start), binding);
}
// ImportSpecifier ::= IdentifierName ("as" Identifier)?
// Identifier "as" Identifier
/**
* @return {ParseTree}
* @private
*/
parseImportSpecifier_() {
let start = this.getTreeStartLocation_();
let token = peekToken();
let isKeyword = token.isKeyword();
let binding;
let name = this.eatIdName_();
if (isKeyword || this.peekPredefinedString_(AS)) {
this.eatId_(AS);
binding = this.parseImportedBinding_();
} else {
binding = new ImportedBinding(name.location,
new BindingIdentifier(name.location, name));
name = null;
}
return new ImportSpecifier(this.getTreeLocation_(start), binding, name);
}
// export VariableStatement
// export FunctionDeclaration
// export ConstStatement
// export ClassDeclaration
/**
* @return {ParseTree}
* @private
*/
parseExportDeclaration_() {
let start = this.getTreeStartLocation_();
this.eat_(EXPORT);
let exportTree;
let annotations = this.popAnnotations_();
let type = peekType();
switch (type) {
case CONST:
case LET:
if (this.options_.blockBinding) {
exportTree = this.parseVariableStatement_();
break;
}
return this.parseUnexpectedToken_();
case VAR:
exportTree = this.parseVariableStatement_();
break;
case FUNCTION:
exportTree = this.parseFunctionDeclaration_();
break;
case CLASS:
exportTree = this.parseClassDeclaration_();
break;
case DEFAULT:
exportTree = this.parseExportDefault_();
break;
case OPEN_CURLY:
case STAR:
exportTree = this.parseNamedExport_();
break;
case IDENTIFIER:
if (this.options_.asyncFunctions && this.peekPredefinedString_(ASYNC)) {
let asyncToken = this.eatId_();
exportTree = this.parseAsyncFunctionDeclaration_(asyncToken);
} else if (this.options_.types && this.peekPredefinedString_(TYPE) &&
peekLookahead(IDENTIFIER)) {
exportTree = this.parseTypeAliasDeclaration_();
} else if (this.options_.exportFromExtended) {
exportTree = this.parseNamedExport_();
} else {
return this.parseUnexpectedToken_();
}
break;
default: {
let token = peekToken();
if (!token.isKeyword()) {
return this.parseUnexpectedToken_();
}
exportTree = this.parseNamedExport_();
}
}
return new ExportDeclaration(this.getTreeLocation_(start), exportTree,
annotations);
}
parseExportDefault_() {
// export default [lookahead ∉ {function, class, from}] AssignmentExpression[In] ;
// export default AssignmentExpression ;
let start = this.getTreeStartLocation_();
let defaultToken = this.eat_(DEFAULT);
if (this.options_.exportFromExtended && this.peekPredefinedString_(FROM)) {
let idName = new IdentifierToken(defaultToken.location, DEFAULT);
let namedExport = new ForwardDefaultExport(this.getTreeLocation_(start), idName);
this.eatId_(FROM);
let moduleSpecifier = this.parseModuleSpecifier_();
return new NamedExport(this.getTreeLocation_(start), namedExport, moduleSpecifier);
}
let exportValue;
switch (peekType()) {
case FUNCTION: {
// Use FunctionExpression as a cover grammar. If it has a name it is
// treated as a declaration.
let tree = this.parseFunctionExpression_();
if (tree.name) {
tree = new FunctionDeclaration(tree.location, tree.name,
tree.functionKind, tree.parameterList,
tree.typeAnnotation, tree.annotations,
tree.body);
}
exportValue = tree;
break;
}
case CLASS: {
if (!this.options_.classes) {
return this.parseSyntaxError_('Unexpected reserved word');
}
// Use ClassExpression as a cover grammar. If it has a name it is
// treated as a declaration.
let tree = this.parseClassExpression_();
if (tree.name) {
tree = new ClassDeclaration(tree.location, tree.name,
tree.superClass, tree.elements,
tree.annotations, tree.typeParameters);
}
exportValue = tree;
break;
}
default:
exportValue = this.parseAssignmentExpression_(ALLOW_IN);
this.eatPossibleImplicitSemiColon_();
}
return new ExportDefault(this.getTreeLocation_(start), exportValue);
}
parseNamedExport_() {
// NamedExport ::=
// "*" "from" ModuleSpecifier
// ExportSpecifierSet
// "*" "from" ModuleSpecifier
// "*" "as" Identifier "from" ModuleSpecifier
// Identifier "from" ModuleSpecifier
let start = this.getTreeStartLocation_();
let exportClause, moduleSpecifier = null;
switch (peekType()) {
case OPEN_CURLY:
exportClause = this.parseExportSpecifierSet_();
if (this.peekPredefinedString_(FROM)) {
this.eatId_(FROM);
moduleSpecifier = this.parseModuleSpecifier_();
} else {
// When there is no `from` the left hand side may not be a keyword
// since it references a local binding.
//
// export {notAKeyword as keywordOK};
//
this.validateExportSpecifierSet_(exportClause);
}
break;
case STAR:
exportClause = this.parseExportStar_();
this.eatId_(FROM);
moduleSpecifier = this.parseModuleSpecifier_();
break;
default: // IDENTIFIER or isKeyword
exportClause = this.parseForwardDefaultExport_();
this.eatId_(FROM);
moduleSpecifier = this.parseModuleSpecifier_();
break;
}
this.eatPossibleImplicitSemiColon_();
return new NamedExport(this.getTreeLocation_(start), exportClause,
moduleSpecifier);
}
parseExportStar_() {
// *
// * as IdentiferName
let start = this.getTreeStartLocation_();
this.eat_(STAR);
if (this.peekPredefinedString_(AS)) {
this.eatId_(AS);
let name = this.eatIdName_();
return new NameSpaceExport(this.getTreeLocation_(start), name);
}
return new ExportStar(this.getTreeLocation_(start));
}
parseExportSpecifierSet_() {
// ExportSpecifierSet ::=
// "{" ExportSpecifier ("," ExportSpecifier)* ","? "}"
let start = this.getTreeStartLocation_();
this.eat_(OPEN_CURLY);
let specifiers = [this.parseExportSpecifier_()];
while (this.eatIf_(COMMA)) {
if (peek(CLOSE_CURLY))
break;
specifiers.push(this.parseExportSpecifier_());
}
this.eat_(CLOSE_CURLY);
return new ExportSpecifierSet(this.getTreeLocation_(start), specifiers);
}
// ExportSpecifier :
// Identifier
// Identifier "as" IdentifierName
parseExportSpecifier_() {
// ExportSpecifier ::= IdentifierName
// | IdentifierName "as" IdentifierName
let start = this.getTreeStartLocation_();
let lhs = this.eatIdName_();
let rhs = null;
if (this.peekPredefinedString_(AS)) {
this.eatId_();
rhs = this.eatIdName_();
}
return new ExportSpecifier(this.getTreeLocation_(start), lhs, rhs);
}
parseForwardDefaultExport_() {
// export IdentifierName from 'module'
let start = this.getTreeStartLocation_();
let idName = this.eatIdName_();
return new ForwardDefaultExport(this.getTreeLocation_(start), idName);
}
validateExportSpecifierSet_(tree) {
for (let i = 0; i < tree.specifiers.length; i++) {
let specifier = tree.specifiers[i];
// These are represented as IdentifierTokens because we used eatIdName.
if (getKeywordType(specifier.lhs.value)) {
this.reportError_(specifier.lhs.location,
`Unexpected token ${specifier.lhs.value}`);
}
}
}
peekId_(type) {
if (type === IDENTIFIER)
return true;
if (this.strictMode_)
return false;
return peekToken().isStrictKeyword();
}
peekIdName_(token) {
return token.type === IDENTIFIER || token.isKeyword();
}
parseClassShared_(constr) {
let start = this.getTreeStartLocation_();
let strictMode = this.strictMode_;
this.strictMode_ = true;
this.eat_(CLASS);
let name = null;
let typeParameters = null;
let annotations = [];
// Name is optional for ClassExpression
if (constr === ClassDeclaration ||
!peek(EXTENDS) && !peek(OPEN_CURLY)) {
name = this.parseBindingIdentifier_();
if (this.options_.types) {
typeParameters = this.parseTypeParametersOpt_();
}
annotations = this.popAnnotations_();
}
let superClass = null;
if (this.eatIf_(EXTENDS)) {
superClass = this.parseLeftHandSideExpression_();
superClass = this.coverFormalsToParenExpression_(superClass);
}
this.eat_(OPEN_CURLY);
let elements = this.parseClassElements_(superClass);
this.eat_(CLOSE_CURLY);
this.strictMode_ = strictMode;
return new constr(this.getTreeLocation_(start), name, superClass,
elements, annotations, typeParameters);
}
/**
* @return {ParseTree}
* @private
*/
parseClassDeclaration_() {
return this.parseClassShared_(ClassDeclaration);
}
/**
* @return {ParseTree}
* @private
*/
parseClassExpression_() {
return this.parseClassShared_(ClassExpression);
}
/**
* @return {Array.<ParseTree>}
* @private
*/
parseClassElements_(derivedClass) {
let result = [];
while (true) {
let type = peekType();
if (type === SEMI_COLON) {
nextToken();
} else if (this.peekClassElement_(peekType())) {
result.push(this.parseClassElement_(derivedClass));
} else {
break;
}
}
return result;
}
peekClassElement_(type) {
// PropertyName covers get, set and static too.
return this.peekPropertyName_(type) ||
type === STAR && this.options_.generators ||
type === AT && this.options_.annotations;
}
// PropertyName :
// LiteralPropertyName
// ComputedPropertyName
parsePropertyName_() {
if (peek(OPEN_SQUARE))
return this.parseComputedPropertyName_()
return this.parseLiteralPropertyName_();
}
parseLiteralPropertyName_() {
let start = this.getTreeStartLocation_();
let token = nextToken();
return new LiteralPropertyName(this.getTreeLocation_(start), token);
}
// ComputedPropertyName :
// [ AssignmentExpression ]
parseComputedPropertyName_() {
let start = this.getTreeStartLocation_();
this.eat_(OPEN_SQUARE);
let expression = this.parseAssignmentExpression_(ALLOW_IN);
this.eat_(CLOSE_SQUARE);
return new ComputedPropertyName(this.getTreeLocation_(start), expression);
}
/**
* Parses a single statement. This statement might be a top level statement
* in a Script or a Module as well as any other statement allowed in a
* FunctionBody.
* @return {ParseTree}
*/
parseStatement() {
// Allow return, yield and await.
let fs = this.pushFunctionState_(FUNCTION_STATE_LENIENT);
let result = this.parseModuleItem_(peekType());
this.popFunctionState_(fs);
return result;
}
/**
* Parses one or more statements. These might be top level statements in a
* Script or a Module as well as any other statement allowed in a
* FunctionBody.
* @return {Array.<ParseTree>}
*/
parseStatements() {
// Allow return, yield and await.
let fs = this.pushFunctionState_(FUNCTION_STATE_LENIENT);
let result = this.parseModuleItemList_();
this.popFunctionState_(fs);
return result;
}
parseStatement_() {
return this.parseStatementWithType_(peekType());
}
/**
* @return {ParseTree}
* @private
*/
parseStatementWithType_(type) {
switch (type) {
// Most common first (based on building Traceur).
case RETURN:
return this.parseReturnStatement_();
case VAR:
return this.parseVariableStatement_();
case IF:
return this.parseIfStatement_();
case FOR:
return this.parseForStatement_();
case BREAK:
return this.parseBreakStatement_();
case SWITCH:
return this.parseSwitchStatement_();
case THROW:
return this.parseThrowStatement_();
case WHILE:
return this.parseWhileStatement_();
// Rest are just alphabetical order.
case AT:
if (this.options_.annotations)
return this.parseAnnotatedDeclarations_(false);
break;
case CONTINUE:
return this.parseContinueStatement_();
case DEBUGGER:
return this.parseDebuggerStatement_();
case DO:
return this.parseDoWhileStatement_();
case OPEN_CURLY:
return this.parseBlock_();
case SEMI_COLON:
return this.parseEmptyStatement_();
case TRY:
return this.parseTryStatement_();
case WITH:
return this.parseWithStatement_();
case INTERFACE:
// TODO(arv): This should only be allowed at the top level.
if (this.options_.types) {
return this.parseInterfaceDeclaration_();
}
}
return this.parseFallThroughStatement_();
}
// 13 Function Definition
/**
* @return {ParseTree}
* @private
*/
parseFunctionDeclaration_() {
return this.parseFunction_(FunctionDeclaration);
}
/**
* @return {ParseTree}
* @private
*/
parseFunctionExpression_() {
return this.parseFunction_(FunctionExpression);
}
parseAsyncFunctionDeclaration_(asyncToken) {
return this.parseAsyncFunction_(asyncToken, FunctionDeclaration);
}
parseAsyncFunctionExpression_(asyncToken) {
return this.parseAsyncFunction_(asyncToken, FunctionExpression);
}
/**
* @return {boolean}
* @private
*/
peekAsyncStar_() {
return this.options_.asyncGenerators && peek(STAR);
}
parseAsyncFunction_(asyncToken, ctor) {
let start = asyncToken.location.start;
this.eat_(FUNCTION);
let kind = FUNCTION_STATE_FUNCTION | FUNCTION_STATE_ASYNC;
if (this.peekAsyncStar_()) {
kind |= FUNCTION_STATE_GENERATOR;
this.eat_(STAR);
asyncToken = new IdentifierToken(asyncToken.location, ASYNC_STAR);
}
let fs = this.pushFunctionState_(kind);
let f = this.parseFunction2_(start, asyncToken, ctor);
this.popFunctionState_(fs);
return f;
}
parseFunction_(ctor) {
let start = this.getTreeStartLocation_();
this.eat_(FUNCTION);
let functionKind = null;
let kind = FUNCTION_STATE_FUNCTION;
if (this.options_.generators && peek(STAR)) {
functionKind = this.eat_(STAR);
kind |= FUNCTION_STATE_GENERATOR;
}
let fs = this.pushFunctionState_(kind);
let f = this.parseFunction2_(start, functionKind, ctor);
this.popFunctionState_(fs);
return f;
}
parseFunction2_(start, functionKind, ctor) {
let name = null;
let annotations = [];
if (ctor === FunctionDeclaration ||
this.peekBindingIdentifier_(peekType())) {
name = this.parseBindingIdentifier_();
annotations = this.popAnnotations_();
}
this.eat_(OPEN_PAREN);
let parameters = this.parseFormalParameters_();
this.eat_(CLOSE_PAREN);
let typeAnnotation = this.parseTypeAnnotationOpt_();
let body = this.parseFunctionBody_(parameters);
return new ctor(this.getTreeLocation_(start), name, functionKind,
parameters, typeAnnotation, annotations, body);
}
peekRest_(type) {
return type === DOT_DOT_DOT && this.options_.restParameters;
}
/**
* @return {FormalParameterList}
* @private
*/
parseFormalParameters_() {
// FormalParameterList :
// [empty]
// FunctionRestParameter
// FormalsList
// FormalsList , FunctionRestParameter
//
// FunctionRestParameter :
// ... BindingIdentifier
//
// FormalsList :
// FormalParameter
// FormalsList , FormalParameter
//
// FormalParameter :
// BindingElement
//
// BindingElement :
// SingleNameBinding
// BindingPattern Initializeropt
let start = this.getTreeStartLocation_();
let formals = [];
this.pushAnnotations_();
let type = peekType();
if (this.peekRest_(type)) {
formals.push(this.parseFormalRestParameter_());
} else {
if (this.peekFormalParameter_(peekType()))
formals.push(this.parseFormalParameter_(INITIALIZER_OPTIONAL));
while (this.eatIf_(COMMA)) {
this.pushAnnotations_();
if (this.peekRest_(peekType())) {
formals.push(this.parseFormalRestParameter_());
break;
}
formals.push(this.parseFormalParameter_(INITIALIZER_OPTIONAL));
}
}
return new FormalParameterList(this.getTreeLocation_(start), formals);
}
peekFormalParameter_(type) {
return this.peekBindingElement_(type);
}
parseFormalParameter_(initializerAllowed) {
let start = this.getTreeStartLocation_();
let binding = this.parseBindingElementBinding_();
let typeAnnotation = this.parseTypeAnnotationOpt_();
let initializer = this.parseBindingElementInitializer_(initializerAllowed);
return new FormalParameter(this.getTreeLocation_(start),
new BindingElement(this.getTreeLocation_(start), binding, initializer),
typeAnnotation, this.popAnnotations_());
}
parseFormalRestParameter_() {
let start = this.getTreeStartLocation_();
let restParameter = this.parseRestParameter_();
let typeAnnotation = this.parseTypeAnnotationOpt_();
return new FormalParameter(this.getTreeLocation_(start), restParameter,
typeAnnotation, this.popAnnotations_());
}
parseRestParameter_() {
let start = this.getTreeStartLocation_();
this.eat_(DOT_DOT_DOT);
let id = this.parseBindingIdentifier_();
let typeAnnotation = this.parseTypeAnnotationOpt_();
return new RestParameter(this.getTreeLocation_(start), id, typeAnnotation);
}
/**
* @return {Block}
* @private
*/
parseFunctionBody_(params) {
let start = this.getTreeStartLocation_();
this.eat_(OPEN_CURLY);
let strictMode = this.strictMode_;
let result = this.parseStatementList_(!strictMode);
validateParameters(params, this.strictMode_, this.errorReporter_);
this.strictMode_ = strictMode;
this.eat_(CLOSE_CURLY);
return new FunctionBody(this.getTreeLocation_(start), result);
}
/**
* @return {SpreadExpression}
* @private
*/
parseSpreadExpression_() {
let start = this.getTreeStartLocation_();
this.eat_(DOT_DOT_DOT);
let operand = this.parseAssignmentExpression_(ALLOW_IN);
return new SpreadExpression(this.getTreeLocation_(start), operand);
}
// 12.1 Block
/**
* @return {Block}
* @private
*/
parseBlock_() {
let start = this.getTreeStartLocation_();
this.eat_(OPEN_CURLY);
let result = this.parseStatementList_(false);
this.eat_(CLOSE_CURLY);
return new Block(this.getTreeLocation_(start), result);
}
// 12.2 Variable Statement
/**
* @return {VariableStatement}
* @private
*/
parseVariableStatement_() {
let start = this.getTreeStartLocation_();
let declarations =
this.parseVariableDeclarationList_(ALLOW_IN, INITIALIZER_REQUIRED);
this.checkInitializers_(declarations);
this.eatPossibleImplicitSemiColon_();
return new VariableStatement(this.getTreeLocation_(start), declarations);
}
/**
* @param {boolean} allowIn
* @param {boolean} initializerRequired Whether destructuring requires an
* initializer
* @return {VariableDeclarationList}
* @private
*/
parseVariableDeclarationList_(allowIn, initializerRequired) {
let type = peekType();
switch (type) {
case CONST:
case LET:
case VAR:
nextToken();
break;
default:
throw Error('unreachable');
}
let start = this.getTreeStartLocation_();
let declarations = [];
declarations.push(this.parseVariableDeclaration_(type, allowIn,
initializerRequired));
while (this.eatIf_(COMMA)) {
declarations.push(this.parseVariableDeclaration_(type, allowIn,
initializerRequired));
}
return new VariableDeclarationList(
this.getTreeLocation_(start), type, declarations);
}
/**
* VariableDeclaration :
* BindingIdentifier Initializeropt
* BindingPattern Initializer
*
* VariableDeclarationNoIn :
* BindingIdentifier InitializerNoInopt
* BindingPattern InitializerNoIn
*
* @param {TokenType} binding
* @param {boolean} noIn
* @param {boolean} initializerRequired
* @return {VariableDeclaration}
* @private
*/
parseVariableDeclaration_(binding, noIn, initializerRequired) {
let initRequired = initializerRequired !== INITIALIZER_OPTIONAL;
let start = this.getTreeStartLocation_();
let lvalue;
let typeAnnotation;
if (this.peekPattern_(peekType())) {
lvalue = this.parseBindingPattern_();
typeAnnotation = null;
} else {
lvalue = this.parseBindingIdentifier_();
typeAnnotation = this.parseTypeAnnotationOpt_();
}
let init = null;
if (peek(EQUAL)) {
init = this.parseInitializer_(noIn);
} else if (lvalue.isPattern() && initRequired) {
this.reportError_(lvalue.location,
'destructuring must have an initializer');
}
return new VariableDeclaration(this.getTreeLocation_(start), lvalue,
typeAnnotation, init);
}
/**
* @param {boolean} allowIn
* @return {ParseTree}
* @private
*/
parseInitializer_(allowIn) {
this.eat_(EQUAL);
return this.parseAssignmentExpression_(allowIn);
}
parseInitializerOpt_(allowIn) {
if (this.eatIf_(EQUAL))
return this.parseAssignmentExpression_(allowIn);
return null;
}
// 12.3 Empty Statement
/**
* @return {EmptyStatement}
* @private
*/
parseEmptyStatement_() {
let start = this.getTreeStartLocation_();
this.eat_(SEMI_COLON);
return new EmptyStatement(this.getTreeLocation_(start));
}
/**
* @return {ExpressionStatement|LabelledStatement}
* @private
*/
parseFallThroughStatement_() {
// ExpressionStatement :
// [lookahead ∉ {{, function, class, let [}] Expression ;
let start = this.getTreeStartLocation_();
let expression;
switch (peekType()) {
case OPEN_CURLY:
return this.parseUnexpectedToken_();
case FUNCTION:
case CLASS:
return this.parseUnexpectedReservedWord_(peekToken());
case LET: {
let token = peekLookahead(OPEN_SQUARE);
if (token) {
return this.parseSyntaxError_(
`A statement cannot start with 'let ['`);
}
}
}
// async [no line terminator] function ...
if (this.options_.asyncFunctions && this.peekPredefinedString_(ASYNC) &&
peekLookahead(FUNCTION)) {
// TODO(arv): This look ahead should not be needed.
let asyncToken = this.eatId_();
let functionToken = peekTokenNoLineTerminator();
if (functionToken !== null)
return this.parseAsyncFunctionDeclaration_(asyncToken);
expression = new IdentifierExpression(this.getTreeLocation_(start),
asyncToken);
} else {
expression = this.parseExpression_(ALLOW_IN);
}
if (expression.type === IDENTIFIER_EXPRESSION) {
// 12.12 Labelled Statement
if (this.eatIf_(COLON)) {
let nameToken = expression.identifierToken;
let statement = this.parseStatement_();
return new LabelledStatement(this.getTreeLocation_(start), nameToken,
statement);
}
}
this.eatPossibleImplicitSemiColon_();
return new ExpressionStatement(this.getTreeLocation_(start), expression);
}
// 12.5 If Statement
/**
* @return {IfStatement}
* @private
*/
parseIfStatement_() {
let start = this.getTreeStartLocation_();
this.eat_(IF);
this.eat_(OPEN_PAREN);
let condition = this.parseExpression_(ALLOW_IN);
this.eat_(CLOSE_PAREN);
let ifClause = this.parseStatement_();
let elseClause = null;
if (this.eatIf_(ELSE)) {
elseClause = this.parseStatement_();
}
return new IfStatement(this.getTreeLocation_(start), condition, ifClause, elseClause);
}
// 12.6 Iteration Statements
// 12.6.1 The do-while Statement
/**
* @return {ParseTree}
* @private
*/
parseDoWhileStatement_() {
let start = this.getTreeStartLocation_();
this.eat_(DO);
let body = this.parseStatement_();
this.eat_(WHILE);
this.eat_(OPEN_PAREN);
let condition = this.parseExpression_(ALLOW_IN);
this.eat_(CLOSE_PAREN);
this.eatPossibleImplicitSemiColon_();
return new DoWhileStatement(this.getTreeLocation_(start), body, condition);
}
// 12.6.2 The while Statement
/**
* @return {ParseTree}
* @private
*/
parseWhileStatement_() {
let start = this.getTreeStartLocation_();
this.eat_(WHILE);
this.eat_(OPEN_PAREN);
let condition = this.parseExpression_(ALLOW_IN);
this.eat_(CLOSE_PAREN);
let body = this.parseStatement_();
return new WhileStatement(this.getTreeLocation_(start), condition, body);
}
// 12.6.3 The for Statement
// 12.6.4 The for-in Statement
// https://github.com/jhusain/asyncgenerator
/**
* @return {ParseTree}
* @private
*/
parseForStatement_() {
let start = this.getTreeStartLocation_();
this.eat_(FOR);
this.eat_(OPEN_PAREN);
let type = peekType();
if (this.peekVariableDeclarationList_(type)) {
let variables = this.parseVariableDeclarationList_(NO_IN,
INITIALIZER_OPTIONAL);
let declarations = variables.declarations;
if (declarations.length > 1 || containsInitializer(declarations)) {
return this.parseForStatement2_(start, variables);
}
type = peekType();
if (type === IN) {
return this.parseForInStatement_(start, variables);
} else if (this.peekOf_()) {
return this.parseForOfStatement_(start, variables);
} else if (this.allowForOn_ && this.peekOn_()) {
return this.parseForOnStatement_(start, variables);
} else {
// for statement: const must have initializers
this.checkInitializers_(variables);
return this.parseForStatement2_(start, variables);
}
}
if (type === SEMI_COLON) {
return this.parseForStatement2_(start, null);
}
let coverInitializedNameCount = this.coverInitializedNameCount_;
let initializer = this.parseExpressionAllowPattern_(NO_IN);
type = peekType();
if ((type === IN || this.peekOf_() ||
this.allowForOn_ && this.peekOn_())) {
initializer = this.transformLeftHandSideExpression_(initializer);
this.validateAssignmentTarget_(initializer, 'assignment');
if (this.peekOf_()) {
return this.parseForOfStatement_(start, initializer);
} else if (this.allowForOn_ && this.peekOn_()) {
return this.parseForOnStatement_(start, initializer);
}
return this.parseForInStatement_(start, initializer);
}
this.ensureNoCoverInitializedNames_(initializer, coverInitializedNameCount);
return this.parseForStatement2_(start, initializer);
}
peekOf_() {
return this.options_.forOf && this.peekPredefinedString_(OF);
}
peekOn_() {
return this.options_.forOn && this.peekPredefinedString_(ON);
}
// The for-each Statement
// for ( { let | let | const } identifier of expression ) statement
/**
* @param {SourcePosition} start
* @param {ParseTree} initializer
* @return {ParseTree}
* @private
*/
parseForOfStatement_(start, initializer) {
this.eatId_(); // of
let collection = this.parseExpression_(ALLOW_IN);
this.eat_(CLOSE_PAREN);
let body = this.parseStatement_();
return new ForOfStatement(this.getTreeLocation_(start), initializer,
collection, body);
}
// The for-on Statement
// for ( { let | let | const } identifier on expression ) statement
/**
* @param {SourcePosition} start
* @param {ParseTree} initializer
* @return {ParseTree}
* @private
*/
parseForOnStatement_(start, initializer) {
this.eatId_(); // on
let observable = this.parseExpression_(ALLOW_IN);
this.eat_(CLOSE_PAREN);
let body = this.parseStatement_();
return new ForOnStatement(this.getTreeLocation_(start), initializer,
observable, body);
}
/**
* Checks variable declaration in variable and for statements.
*
* @param {VariableDeclarationList} variables
* @return {void}
* @private
*/
checkInitializers_(variables) {
if (this.options_.blockBinding &&
variables.declarationType === CONST) {
let type = variables.declarationType;
for (let i = 0; i < variables.declarations.length; i++) {
if (!this.checkInitializer_(type, variables.declarations[i])) {
break;
}
}
}
}
/**
* Checks variable declaration
*
* @param {TokenType} type
* @param {VariableDeclaration} declaration
* @return {boolan} Whether the initializer is correct.
* @private
*/
checkInitializer_(type, declaration) {
if (this.options_.blockBinding && type === CONST &&
declaration.initializer === null) {
this.reportError_(declaration.location,
'const variables must have an initializer');
return false;
}
return true;
}
/**
* @return {boolean}
* @private
*/
peekVariableDeclarationList_(type) {
switch (type) {
case VAR:
return true;
case CONST:
case LET:
return this.options_.blockBinding;
default:
return false;
}
}
// 12.6.3 The for Statement
/**
* @param {SourcePosition} start
* @param {ParseTree} initializer
* @return {ParseTree}
* @private
*/
parseForStatement2_(start, initializer) {
this.eat_(SEMI_COLON);
let condition = null;
if (!peek(SEMI_COLON)) {
condition = this.parseExpression_(ALLOW_IN);
}
this.eat_(SEMI_COLON);
let increment = null;
if (!peek(CLOSE_PAREN)) {
increment = this.parseExpression_(ALLOW_IN);
}
this.eat_(CLOSE_PAREN);
let body = this.parseStatement_();
return new ForStatement(this.getTreeLocation_(start), initializer,
condition, increment, body);
}
// 12.6.4 The for-in Statement
/**
* @param {SourcePosition} start
* @param {ParseTree} initializer
* @return {ParseTree}
* @private
*/
parseForInStatement_(start, initializer) {
this.eat_(IN);
let collection = this.parseExpression_(ALLOW_IN);
this.eat_(CLOSE_PAREN);
let body = this.parseStatement_();
return new ForInStatement(this.getTreeLocation_(start), initializer,
collection, body);
}
// 12.7 The continue Statement
/**
* @return {ParseTree}
* @private
*/
parseContinueStatement_() {
let start = this.getTreeStartLocation_();
this.eat_(CONTINUE);
let name = null;
if (!this.peekImplicitSemiColon_()) {
name = this.eatIdOpt_();
}
this.eatPossibleImplicitSemiColon_();
return new ContinueStatement(this.getTreeLocation_(start), name);
}
// 12.8 The break Statement
/**
* @return {ParseTree}
* @private
*/
parseBreakStatement_() {
let start = this.getTreeStartLocation_();
this.eat_(BREAK);
let name = null;
if (!this.peekImplicitSemiColon_()) {
name = this.eatIdOpt_();
}
this.eatPossibleImplicitSemiColon_();
return new BreakStatement(this.getTreeLocation_(start), name);
}
//12.9 The return Statement
/**
* @return {ParseTree}
* @private
*/
parseReturnStatement_() {
let start = this.getTreeStartLocation_();
let returnToken = this.eat_(RETURN);
if (this.functionState_.isTopMost()) {
this.reportError_(returnToken.location, 'Illegal return statement');
}
let expression = null;
if (!this.peekImplicitSemiColon_()) {
expression = this.parseExpression_(ALLOW_IN);
}
this.eatPossibleImplicitSemiColon_();
return new ReturnStatement(this.getTreeLocation_(start), expression);
}
/**
* YieldExpression[In] :
* yield
* yield [no LineTerminator here] AssignmentExpression[?In, Yield]
* yield [no LineTerminator here] * AssignmentExpression[?In, Yield]
*
* @param {boolean} allowIn
* @return {ParseTree}
* @private
*/
parseYieldExpression_(allowIn) {
let start = this.getTreeStartLocation_();