UNPKG

traceur

Version:
1,836 lines (1,671 loc) 131 kB
// 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_();