lambdaworld-archive/node_modules/shift-parser/src/parser.js

2651 lines
90 KiB
JavaScript

/**
* Copyright 2014 Shape Security, Inc.
*
* 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.
*/
const { ErrorMessages } = require('./errors');
const acceptRegex = require('shift-regexp-acceptor');
const { Tokenizer, TokenClass, TokenType } = require('./tokenizer');
const AST = require('shift-ast');
// Empty parameter list for ArrowExpression
const ARROW_EXPRESSION_PARAMS = 'CoverParenthesizedExpressionAndArrowParameterList';
const EXPORT_UNKNOWN_SPECIFIER = 'ExportNameOfUnknownType';
const Precedence = {
Sequence: 0,
Yield: 1,
Assignment: 1,
Conditional: 2,
ArrowFunction: 2,
LogicalOR: 3,
LogicalAND: 4,
BitwiseOR: 5,
BitwiseXOR: 6,
BitwiseAND: 7,
Equality: 8,
Relational: 9,
BitwiseSHIFT: 10,
Additive: 11,
Multiplicative: 12,
Unary: 13,
Postfix: 14,
Call: 15,
New: 16,
TaggedTemplate: 17,
Member: 18,
Primary: 19,
};
const BinaryPrecedence = {
'||': Precedence.LogicalOR,
'&&': Precedence.LogicalAND,
'|': Precedence.BitwiseOR,
'^': Precedence.BitwiseXOR,
'&': Precedence.BitwiseAND,
'==': Precedence.Equality,
'!=': Precedence.Equality,
'===': Precedence.Equality,
'!==': Precedence.Equality,
'<': Precedence.Relational,
'>': Precedence.Relational,
'<=': Precedence.Relational,
'>=': Precedence.Relational,
'in': Precedence.Relational,
'instanceof': Precedence.Relational,
'<<': Precedence.BitwiseSHIFT,
'>>': Precedence.BitwiseSHIFT,
'>>>': Precedence.BitwiseSHIFT,
'+': Precedence.Additive,
'-': Precedence.Additive,
'*': Precedence.Multiplicative,
'%': Precedence.Multiplicative,
'/': Precedence.Multiplicative,
};
function isValidSimpleAssignmentTarget(node) {
if (node == null) return false;
switch (node.type) {
case 'IdentifierExpression':
case 'ComputedMemberExpression':
case 'StaticMemberExpression':
return true;
}
return false;
}
function isPrefixOperator(token) {
switch (token.type) {
case TokenType.INC:
case TokenType.DEC:
case TokenType.ADD:
case TokenType.SUB:
case TokenType.BIT_NOT:
case TokenType.NOT:
case TokenType.DELETE:
case TokenType.VOID:
case TokenType.TYPEOF:
return true;
}
return false;
}
function isUpdateOperator(token) {
return token.type === TokenType.INC || token.type === TokenType.DEC;
}
class GenericParser extends Tokenizer {
constructor(source) {
super(source);
this.allowIn = true;
this.inFunctionBody = false;
this.inParameter = false;
this.allowYieldExpression = false;
this.allowAwaitExpression = false;
this.firstAwaitLocation = null; // for forbidding `await` in async arrow params.
this.module = false;
this.moduleIsTheGoalSymbol = false;
this.strict = false;
// Cover grammar
this.isBindingElement = true;
this.isAssignmentTarget = true;
this.firstExprError = null;
}
match(subType) {
return this.lookahead.type === subType;
}
matchIdentifier() {
switch (this.lookahead.type) {
case TokenType.IDENTIFIER:
case TokenType.LET:
case TokenType.YIELD:
case TokenType.ASYNC:
return true;
case TokenType.AWAIT:
if (!this.moduleIsTheGoalSymbol) {
if (this.firstAwaitLocation === null) {
this.firstAwaitLocation = this.getLocation();
}
return true;
}
return false;
case TokenType.ESCAPED_KEYWORD:
if (this.lookahead.value === 'await' && !this.moduleIsTheGoalSymbol) {
if (this.firstAwaitLocation === null) {
this.firstAwaitLocation = this.getLocation();
}
return true;
}
return this.lookahead.value === 'let'
|| this.lookahead.value === 'yield'
|| this.lookahead.value === 'async';
}
return false;
}
eat(tokenType) {
if (this.lookahead.type === tokenType) {
return this.lex();
}
return null;
}
expect(tokenType) {
if (this.lookahead.type === tokenType) {
return this.lex();
}
throw this.createUnexpected(this.lookahead);
}
matchContextualKeyword(keyword) {
return this.lookahead.type === TokenType.IDENTIFIER && !this.lookahead.escaped && this.lookahead.value === keyword;
}
expectContextualKeyword(keyword) {
if (this.lookahead.type === TokenType.IDENTIFIER && !this.lookahead.escaped && this.lookahead.value === keyword) {
return this.lex();
}
throw this.createUnexpected(this.lookahead);
}
eatContextualKeyword(keyword) {
if (this.lookahead.type === TokenType.IDENTIFIER && !this.lookahead.escaped && this.lookahead.value === keyword) {
return this.lex();
}
return null;
}
consumeSemicolon() {
if (this.eat(TokenType.SEMICOLON)) return;
if (this.hasLineTerminatorBeforeNext) return;
if (!this.eof() && !this.match(TokenType.RBRACE)) {
throw this.createUnexpected(this.lookahead);
}
}
// this is a no-op, reserved for future use
startNode(node) {
return node;
}
copyNode(src, dest) {
return dest;
}
finishNode(node /* , startState */) {
return node;
}
parseModule() {
this.moduleIsTheGoalSymbol = this.module = this.strict = true;
this.lookahead = this.advance();
let startState = this.startNode();
let { directives, statements } = this.parseBody();
if (!this.match(TokenType.EOS)) {
throw this.createUnexpected(this.lookahead);
}
return this.finishNode(new AST.Module({ directives, items: statements }), startState);
}
parseScript() {
this.lookahead = this.advance();
let startState = this.startNode();
let { directives, statements } = this.parseBody();
if (!this.match(TokenType.EOS)) {
throw this.createUnexpected(this.lookahead);
}
return this.finishNode(new AST.Script({ directives, statements }), startState);
}
parseFunctionBody() {
let oldInFunctionBody = this.inFunctionBody;
let oldModule = this.module;
let oldStrict = this.strict;
this.inFunctionBody = true;
this.module = false;
let startState = this.startNode();
this.expect(TokenType.LBRACE);
let body = new AST.FunctionBody(this.parseBody());
this.expect(TokenType.RBRACE);
body = this.finishNode(body, startState);
this.inFunctionBody = oldInFunctionBody;
this.module = oldModule;
this.strict = oldStrict;
return body;
}
parseBody() {
let directives = [], statements = [], parsingDirectives = true, directiveOctal = null;
while (true) {
if (this.eof() || this.match(TokenType.RBRACE)) break;
let token = this.lookahead;
let text = token.slice.text;
let isStringLiteral = token.type === TokenType.STRING;
let isModule = this.module;
let directiveLocation = this.getLocation();
let directiveStartState = this.startNode();
let stmt = isModule ? this.parseModuleItem() : this.parseStatementListItem();
if (parsingDirectives) {
if (isStringLiteral && stmt.type === 'ExpressionStatement' && stmt.expression.type === 'LiteralStringExpression') {
if (!directiveOctal && token.octal) {
directiveOctal = this.createErrorWithLocation(directiveLocation, 'Unexpected legacy octal escape sequence: \\' + token.octal);
}
let rawValue = text.slice(1, -1);
if (rawValue === 'use strict') {
this.strict = true;
}
directives.push(this.finishNode(new AST.Directive({ rawValue }), directiveStartState));
} else {
parsingDirectives = false;
if (directiveOctal && this.strict) {
throw directiveOctal;
}
statements.push(stmt);
}
} else {
statements.push(stmt);
}
}
if (directiveOctal && this.strict) {
throw directiveOctal;
}
return { directives, statements };
}
parseImportSpecifier() {
let startState = this.startNode(), name;
if (this.matchIdentifier()) {
name = this.parseIdentifier();
if (!this.eatContextualKeyword('as')) {
return this.finishNode(new AST.ImportSpecifier({
name: null,
binding: this.finishNode(new AST.BindingIdentifier({ name }), startState),
}), startState);
}
} else if (this.lookahead.type.klass.isIdentifierName) {
name = this.parseIdentifierName();
this.expectContextualKeyword('as');
}
return this.finishNode(new AST.ImportSpecifier({ name, binding: this.parseBindingIdentifier() }), startState);
}
parseNameSpaceBinding() {
this.expect(TokenType.MUL);
this.expectContextualKeyword('as');
return this.parseBindingIdentifier();
}
parseNamedImports() {
let result = [];
this.expect(TokenType.LBRACE);
while (!this.eat(TokenType.RBRACE)) {
result.push(this.parseImportSpecifier());
if (!this.eat(TokenType.COMMA)) {
this.expect(TokenType.RBRACE);
break;
}
}
return result;
}
parseFromClause() {
this.expectContextualKeyword('from');
let value = this.expect(TokenType.STRING).str;
return value;
}
parseImportDeclaration() {
let startState = this.startNode(), defaultBinding = null, moduleSpecifier;
this.expect(TokenType.IMPORT);
if (this.match(TokenType.STRING)) {
moduleSpecifier = this.lex().str;
this.consumeSemicolon();
return this.finishNode(new AST.Import({ defaultBinding: null, namedImports: [], moduleSpecifier }), startState);
}
if (this.matchIdentifier()) {
defaultBinding = this.parseBindingIdentifier();
if (!this.eat(TokenType.COMMA)) {
let decl = new AST.Import({ defaultBinding, namedImports: [], moduleSpecifier: this.parseFromClause() });
this.consumeSemicolon();
return this.finishNode(decl, startState);
}
}
if (this.match(TokenType.MUL)) {
let decl = new AST.ImportNamespace({
defaultBinding,
namespaceBinding: this.parseNameSpaceBinding(),
moduleSpecifier: this.parseFromClause(),
});
this.consumeSemicolon();
return this.finishNode(decl, startState);
} else if (this.match(TokenType.LBRACE)) {
let decl = new AST.Import({
defaultBinding,
namedImports: this.parseNamedImports(),
moduleSpecifier: this.parseFromClause(),
});
this.consumeSemicolon();
return this.finishNode(decl, startState);
}
throw this.createUnexpected(this.lookahead);
}
parseExportSpecifier() {
let startState = this.startNode();
let name = this.finishNode({ type: EXPORT_UNKNOWN_SPECIFIER, isIdentifier: this.matchIdentifier(), value: this.parseIdentifierName() }, startState);
if (this.eatContextualKeyword('as')) {
let exportedName = this.parseIdentifierName();
return this.finishNode({ name, exportedName }, startState);
}
return this.finishNode({ name, exportedName: null }, startState);
}
parseExportClause() {
this.expect(TokenType.LBRACE);
let result = [];
while (!this.eat(TokenType.RBRACE)) {
result.push(this.parseExportSpecifier());
if (!this.eat(TokenType.COMMA)) {
this.expect(TokenType.RBRACE);
break;
}
}
return result;
}
parseExportDeclaration() {
let startState = this.startNode(), decl;
this.expect(TokenType.EXPORT);
switch (this.lookahead.type) {
case TokenType.MUL:
this.lex();
// export * FromClause ;
decl = new AST.ExportAllFrom({ moduleSpecifier: this.parseFromClause() });
this.consumeSemicolon();
break;
case TokenType.LBRACE: {
// export ExportClause FromClause ;
// export ExportClause ;
let namedExports = this.parseExportClause();
let moduleSpecifier = null;
if (this.matchContextualKeyword('from')) {
moduleSpecifier = this.parseFromClause();
decl = new AST.ExportFrom({ namedExports: namedExports.map(e => this.copyNode(e, new AST.ExportFromSpecifier({ name: e.name.value, exportedName: e.exportedName }))), moduleSpecifier });
} else {
namedExports.forEach(({ name }) => {
if (!name.isIdentifier) {
throw this.createError(ErrorMessages.ILLEGAL_EXPORTED_NAME);
}
});
decl = new AST.ExportLocals({ namedExports: namedExports.map(e => this.copyNode(e, new AST.ExportLocalSpecifier({ name: this.copyNode(e.name, new AST.IdentifierExpression({ name: e.name.value })), exportedName: e.exportedName }))) });
}
this.consumeSemicolon();
break;
}
case TokenType.CLASS:
// export ClassDeclaration
decl = new AST.Export({ declaration: this.parseClass({ isExpr: false, inDefault: false }) });
break;
case TokenType.FUNCTION:
// export HoistableDeclaration
decl = new AST.Export({ declaration: this.parseFunction({ isExpr: false, inDefault: false, allowGenerator: true, isAsync: false }) });
break;
case TokenType.ASYNC: {
let preAsyncStartState = this.startNode();
this.lex();
decl = new AST.Export({ declaration: this.parseFunction({ isExpr: false, inDefault: false, allowGenerator: true, isAsync: true, startState: preAsyncStartState }) });
break;
}
case TokenType.DEFAULT:
this.lex();
switch (this.lookahead.type) {
case TokenType.FUNCTION:
// export default HoistableDeclaration[Default]
decl = new AST.ExportDefault({
body: this.parseFunction({ isExpr: false, inDefault: true, allowGenerator: true, isAsync: false }),
});
break;
case TokenType.CLASS:
// export default ClassDeclaration[Default]
decl = new AST.ExportDefault({ body: this.parseClass({ isExpr: false, inDefault: true }) });
break;
case TokenType.ASYNC: {
let preAsyncStartState = this.startNode();
let lexerState = this.saveLexerState();
this.lex();
if (!this.hasLineTerminatorBeforeNext && this.match(TokenType.FUNCTION)) {
decl = new AST.ExportDefault({
body: this.parseFunction({ isExpr: false, inDefault: true, allowGenerator: false, isAsync: true, startState: preAsyncStartState }),
});
break;
}
this.restoreLexerState(lexerState);
}
// else fall through
default:
// export default [lookahead ∉ {function, async [no LineTerminatorHere] function, class}] AssignmentExpression[In] ;
decl = new AST.ExportDefault({ body: this.parseAssignmentExpression() });
this.consumeSemicolon();
break;
}
break;
case TokenType.VAR:
case TokenType.LET:
case TokenType.CONST:
// export LexicalDeclaration
decl = new AST.Export({ declaration: this.parseVariableDeclaration(true) });
this.consumeSemicolon();
break;
default:
throw this.createUnexpected(this.lookahead);
}
return this.finishNode(decl, startState);
}
parseModuleItem() {
switch (this.lookahead.type) {
case TokenType.IMPORT:
return this.parseImportDeclaration();
case TokenType.EXPORT:
return this.parseExportDeclaration();
default:
return this.parseStatementListItem();
}
}
lookaheadLexicalDeclaration() {
if (this.match(TokenType.LET) || this.match(TokenType.CONST)) {
let lexerState = this.saveLexerState();
this.lex();
if (
this.matchIdentifier() ||
this.match(TokenType.LBRACE) ||
this.match(TokenType.LBRACK)
) {
this.restoreLexerState(lexerState);
return true;
}
this.restoreLexerState(lexerState);
}
return false;
}
parseStatementListItem() {
if (this.eof()) throw this.createUnexpected(this.lookahead);
switch (this.lookahead.type) {
case TokenType.FUNCTION:
return this.parseFunction({ isExpr: false, inDefault: false, allowGenerator: true, isAsync: false });
case TokenType.CLASS:
return this.parseClass({ isExpr: false, inDefault: false });
case TokenType.ASYNC: {
let preAsyncStartState = this.getLocation();
let lexerState = this.saveLexerState();
this.lex();
if (!this.hasLineTerminatorBeforeNext && this.match(TokenType.FUNCTION)) {
return this.parseFunction({ isExpr: false, inDefault: false, allowGenerator: true, isAsync: true, startState: preAsyncStartState });
}
this.restoreLexerState(lexerState);
return this.parseStatement();
}
default:
if (this.lookaheadLexicalDeclaration()) {
let startState = this.startNode();
return this.finishNode(this.parseVariableDeclarationStatement(), startState);
}
return this.parseStatement();
}
}
parseStatement() {
let startState = this.startNode();
let stmt = this.isolateCoverGrammar(this.parseStatementHelper);
return this.finishNode(stmt, startState);
}
parseStatementHelper() {
if (this.eof()) {
throw this.createUnexpected(this.lookahead);
}
switch (this.lookahead.type) {
case TokenType.SEMICOLON:
return this.parseEmptyStatement();
case TokenType.LBRACE:
return this.parseBlockStatement();
case TokenType.LPAREN:
return this.parseExpressionStatement();
case TokenType.BREAK:
return this.parseBreakStatement();
case TokenType.CONTINUE:
return this.parseContinueStatement();
case TokenType.DEBUGGER:
return this.parseDebuggerStatement();
case TokenType.DO:
return this.parseDoWhileStatement();
case TokenType.FOR:
return this.parseForStatement();
case TokenType.IF:
return this.parseIfStatement();
case TokenType.RETURN:
return this.parseReturnStatement();
case TokenType.SWITCH:
return this.parseSwitchStatement();
case TokenType.THROW:
return this.parseThrowStatement();
case TokenType.TRY:
return this.parseTryStatement();
case TokenType.VAR:
return this.parseVariableDeclarationStatement();
case TokenType.WHILE:
return this.parseWhileStatement();
case TokenType.WITH:
return this.parseWithStatement();
case TokenType.FUNCTION:
case TokenType.CLASS:
throw this.createUnexpected(this.lookahead);
default: {
let lexerState = this.saveLexerState();
if (this.eat(TokenType.LET)) {
if (this.match(TokenType.LBRACK)) {
this.restoreLexerState(lexerState);
throw this.createUnexpected(this.lookahead);
}
this.restoreLexerState(lexerState);
} else if (this.eat(TokenType.ASYNC)) {
if (!this.hasLineTerminatorBeforeNext && this.match(TokenType.FUNCTION)) {
throw this.createUnexpected(this.lookahead);
}
this.restoreLexerState(lexerState);
}
let expr = this.parseExpression();
// 12.12 Labelled Statements;
if (expr.type === 'IdentifierExpression' && this.eat(TokenType.COLON)) {
let labeledBody = this.match(TokenType.FUNCTION)
? this.parseFunction({ isExpr: false, inDefault: false, allowGenerator: false, isAsync: false })
: this.parseStatement();
return new AST.LabeledStatement({ label: expr.name, body: labeledBody });
}
this.consumeSemicolon();
return new AST.ExpressionStatement({ expression: expr });
}
}
}
parseEmptyStatement() {
this.lex();
return new AST.EmptyStatement;
}
parseBlockStatement() {
return new AST.BlockStatement({ block: this.parseBlock() });
}
parseExpressionStatement() {
let expr = this.parseExpression();
this.consumeSemicolon();
return new AST.ExpressionStatement({ expression: expr });
}
parseBreakStatement() {
this.lex();
// Catch the very common case first: immediately a semicolon (U+003B).
if (this.eat(TokenType.SEMICOLON) || this.hasLineTerminatorBeforeNext) {
return new AST.BreakStatement({ label: null });
}
let label = null;
if (this.matchIdentifier()) {
label = this.parseIdentifier();
}
this.consumeSemicolon();
return new AST.BreakStatement({ label });
}
parseContinueStatement() {
this.lex();
// Catch the very common case first: immediately a semicolon (U+003B).
if (this.eat(TokenType.SEMICOLON) || this.hasLineTerminatorBeforeNext) {
return new AST.ContinueStatement({ label: null });
}
let label = null;
if (this.matchIdentifier()) {
label = this.parseIdentifier();
}
this.consumeSemicolon();
return new AST.ContinueStatement({ label });
}
parseDebuggerStatement() {
this.lex();
this.consumeSemicolon();
return new AST.DebuggerStatement;
}
parseDoWhileStatement() {
this.lex();
let body = this.parseStatement();
this.expect(TokenType.WHILE);
this.expect(TokenType.LPAREN);
let test = this.parseExpression();
this.expect(TokenType.RPAREN);
this.eat(TokenType.SEMICOLON);
return new AST.DoWhileStatement({ body, test });
}
parseForStatement() {
this.lex();
let isAwait = this.allowAwaitExpression && this.eat(TokenType.AWAIT);
this.expect(TokenType.LPAREN);
let test = null;
let right = null;
if (isAwait && this.match(TokenType.SEMICOLON)) {
throw this.createUnexpected(this.lookahead);
}
if (this.eat(TokenType.SEMICOLON)) {
if (!this.match(TokenType.SEMICOLON)) {
test = this.parseExpression();
}
this.expect(TokenType.SEMICOLON);
if (!this.match(TokenType.RPAREN)) {
right = this.parseExpression();
}
return new AST.ForStatement({ init: null, test, update: right, body: this.getIteratorStatementEpilogue() });
}
let startsWithLet = this.match(TokenType.LET);
let isForDecl = this.lookaheadLexicalDeclaration();
let leftStartState = this.startNode();
if (this.match(TokenType.VAR) || isForDecl) {
let previousAllowIn = this.allowIn;
this.allowIn = false;
let init = this.parseVariableDeclaration(false);
this.allowIn = previousAllowIn;
if (init.declarators.length === 1 && (this.match(TokenType.IN) || this.matchContextualKeyword('of'))) {
let ctor;
let decl = init.declarators[0];
if (this.match(TokenType.IN)) {
if (isAwait) {
throw this.createUnexpected(this.lookahead);
}
if (decl.init !== null && (this.strict || init.kind !== 'var' || decl.binding.type !== 'BindingIdentifier')) {
throw this.createError(ErrorMessages.INVALID_VAR_INIT_FOR_IN);
}
ctor = AST.ForInStatement;
this.lex();
right = this.parseExpression();
} else {
if (decl.init !== null) {
throw this.createError(isAwait ? ErrorMessages.INVALID_VAR_INIT_FOR_AWAIT : ErrorMessages.INVALID_VAR_INIT_FOR_OF);
}
if (isAwait) {
ctor = AST.ForAwaitStatement;
} else {
ctor = AST.ForOfStatement;
}
this.lex();
right = this.parseAssignmentExpression();
}
let body = this.getIteratorStatementEpilogue();
return new ctor({ left: init, right, body });
} else if (isAwait) {
throw this.createUnexpected(this.lookahead);
}
this.expect(TokenType.SEMICOLON);
if (init.declarators.some(decl => decl.binding.type !== 'BindingIdentifier' && decl.init === null)) {
throw this.createError(ErrorMessages.UNINITIALIZED_BINDINGPATTERN_IN_FOR_INIT);
}
if (!this.match(TokenType.SEMICOLON)) {
test = this.parseExpression();
}
this.expect(TokenType.SEMICOLON);
if (!this.match(TokenType.RPAREN)) {
right = this.parseExpression();
}
return new AST.ForStatement({ init, test, update: right, body: this.getIteratorStatementEpilogue() });
}
let previousAllowIn = this.allowIn;
this.allowIn = false;
let expr = this.inheritCoverGrammar(this.parseAssignmentExpressionOrTarget);
this.allowIn = previousAllowIn;
if (this.isAssignmentTarget && expr.type !== 'AssignmentExpression' && (this.match(TokenType.IN) || this.matchContextualKeyword('of'))) {
if (expr.type === 'ObjectAssignmentTarget' || expr.type === 'ArrayAssignmentTarget') {
this.firstExprError = null;
}
if (startsWithLet && this.matchContextualKeyword('of')) {
throw this.createError(isAwait ? ErrorMessages.INVALID_LHS_IN_FOR_AWAIT : ErrorMessages.INVALID_LHS_IN_FOR_OF);
}
let ctor;
if (this.match(TokenType.IN)) {
if (isAwait) {
throw this.createUnexpected(this.lookahead);
}
ctor = AST.ForInStatement;
this.lex();
right = this.parseExpression();
} else {
if (isAwait) {
ctor = AST.ForAwaitStatement;
} else {
ctor = AST.ForOfStatement;
}
this.lex();
right = this.parseAssignmentExpression();
}
return new ctor({ left: this.transformDestructuring(expr), right, body: this.getIteratorStatementEpilogue() });
} else if (isAwait) {
throw this.createError(ErrorMessages.INVALID_LHS_IN_FOR_AWAIT);
}
if (this.firstExprError) {
throw this.firstExprError;
}
while (this.eat(TokenType.COMMA)) {
let rhs = this.parseAssignmentExpression();
expr = this.finishNode(new AST.BinaryExpression({ left: expr, operator: ',', right: rhs }), leftStartState);
}
if (this.match(TokenType.IN)) {
throw this.createError(ErrorMessages.INVALID_LHS_IN_FOR_IN);
}
if (this.matchContextualKeyword('of')) {
throw this.createError(ErrorMessages.INVALID_LHS_IN_FOR_OF);
}
this.expect(TokenType.SEMICOLON);
if (!this.match(TokenType.SEMICOLON)) {
test = this.parseExpression();
}
this.expect(TokenType.SEMICOLON);
if (!this.match(TokenType.RPAREN)) {
right = this.parseExpression();
}
return new AST.ForStatement({ init: expr, test, update: right, body: this.getIteratorStatementEpilogue() });
}
getIteratorStatementEpilogue() {
this.expect(TokenType.RPAREN);
let body = this.parseStatement();
return body;
}
parseIfStatementChild() {
return this.match(TokenType.FUNCTION)
? this.parseFunction({ isExpr: false, inDefault: false, allowGenerator: false, isAsync: false })
: this.parseStatement();
}
parseIfStatement() {
this.lex();
this.expect(TokenType.LPAREN);
let test = this.parseExpression();
this.expect(TokenType.RPAREN);
let consequent = this.parseIfStatementChild();
let alternate = null;
if (this.eat(TokenType.ELSE)) {
alternate = this.parseIfStatementChild();
}
return new AST.IfStatement({ test, consequent, alternate });
}
parseReturnStatement() {
if (!this.inFunctionBody) {
throw this.createError(ErrorMessages.ILLEGAL_RETURN);
}
this.lex();
// Catch the very common case first: immediately a semicolon (U+003B).
if (this.eat(TokenType.SEMICOLON) || this.hasLineTerminatorBeforeNext) {
return new AST.ReturnStatement({ expression: null });
}
let expression = null;
if (!this.match(TokenType.RBRACE) && !this.eof()) {
expression = this.parseExpression();
}
this.consumeSemicolon();
return new AST.ReturnStatement({ expression });
}
parseSwitchStatement() {
this.lex();
this.expect(TokenType.LPAREN);
let discriminant = this.parseExpression();
this.expect(TokenType.RPAREN);
this.expect(TokenType.LBRACE);
if (this.eat(TokenType.RBRACE)) {
return new AST.SwitchStatement({ discriminant, cases: [] });
}
let cases = this.parseSwitchCases();
if (this.match(TokenType.DEFAULT)) {
let defaultCase = this.parseSwitchDefault();
let postDefaultCases = this.parseSwitchCases();
if (this.match(TokenType.DEFAULT)) {
throw this.createError(ErrorMessages.MULTIPLE_DEFAULTS_IN_SWITCH);
}
this.expect(TokenType.RBRACE);
return new AST.SwitchStatementWithDefault({
discriminant,
preDefaultCases: cases,
defaultCase,
postDefaultCases,
});
}
this.expect(TokenType.RBRACE);
return new AST.SwitchStatement({ discriminant, cases });
}
parseSwitchCases() {
let result = [];
while (!(this.eof() || this.match(TokenType.RBRACE) || this.match(TokenType.DEFAULT))) {
result.push(this.parseSwitchCase());
}
return result;
}
parseSwitchCase() {
let startState = this.startNode();
this.expect(TokenType.CASE);
return this.finishNode(new AST.SwitchCase({
test: this.parseExpression(),
consequent: this.parseSwitchCaseBody(),
}), startState);
}
parseSwitchDefault() {
let startState = this.startNode();
this.expect(TokenType.DEFAULT);
return this.finishNode(new AST.SwitchDefault({ consequent: this.parseSwitchCaseBody() }), startState);
}
parseSwitchCaseBody() {
this.expect(TokenType.COLON);
return this.parseStatementListInSwitchCaseBody();
}
parseStatementListInSwitchCaseBody() {
let result = [];
while (!(this.eof() || this.match(TokenType.RBRACE) || this.match(TokenType.DEFAULT) || this.match(TokenType.CASE))) {
result.push(this.parseStatementListItem());
}
return result;
}
parseThrowStatement() {
let token = this.lex();
if (this.hasLineTerminatorBeforeNext) {
throw this.createErrorWithLocation(token, ErrorMessages.NEWLINE_AFTER_THROW);
}
let expression = this.parseExpression();
this.consumeSemicolon();
return new AST.ThrowStatement({ expression });
}
parseTryStatement() {
this.lex();
let body = this.parseBlock();
if (this.match(TokenType.CATCH)) {
let catchClause = this.parseCatchClause();
if (this.eat(TokenType.FINALLY)) {
let finalizer = this.parseBlock();
return new AST.TryFinallyStatement({ body, catchClause, finalizer });
}
return new AST.TryCatchStatement({ body, catchClause });
}
if (this.eat(TokenType.FINALLY)) {
let finalizer = this.parseBlock();
return new AST.TryFinallyStatement({ body, catchClause: null, finalizer });
}
throw this.createError(ErrorMessages.NO_CATCH_OR_FINALLY);
}
parseVariableDeclarationStatement() {
let declaration = this.parseVariableDeclaration(true);
this.consumeSemicolon();
return new AST.VariableDeclarationStatement({ declaration });
}
parseWhileStatement() {
this.lex();
this.expect(TokenType.LPAREN);
let test = this.parseExpression();
let body = this.getIteratorStatementEpilogue();
return new AST.WhileStatement({ test, body });
}
parseWithStatement() {
this.lex();
this.expect(TokenType.LPAREN);
let object = this.parseExpression();
this.expect(TokenType.RPAREN);
let body = this.parseStatement();
return new AST.WithStatement({ object, body });
}
parseCatchClause() {
let startState = this.startNode();
this.lex();
let binding = null;
// Catch binding is optional
if (this.match(TokenType.LPAREN)) {
this.lex();
if (this.match(TokenType.RPAREN) || this.match(TokenType.LPAREN)) {
throw this.createUnexpected(this.lookahead);
}
binding = this.parseBindingTarget();
this.expect(TokenType.RPAREN);
}
let body = this.parseBlock();
return this.finishNode(new AST.CatchClause({ binding, body }), startState);
}
parseBlock() {
let startState = this.startNode();
this.expect(TokenType.LBRACE);
let body = [];
while (!this.match(TokenType.RBRACE)) {
body.push(this.parseStatementListItem());
}
this.expect(TokenType.RBRACE);
return this.finishNode(new AST.Block({ statements: body }), startState);
}
parseVariableDeclaration(bindingPatternsMustHaveInit) {
let startState = this.startNode();
let token = this.lex();
// preceded by this.match(TokenSubType.VAR) || this.match(TokenSubType.LET);
let kind = token.type === TokenType.VAR ? 'var' : token.type === TokenType.CONST ? 'const' : 'let';
let declarators = this.parseVariableDeclaratorList(bindingPatternsMustHaveInit);
return this.finishNode(new AST.VariableDeclaration({ kind, declarators }), startState);
}
parseVariableDeclaratorList(bindingPatternsMustHaveInit) {
let result = [];
do {
result.push(this.parseVariableDeclarator(bindingPatternsMustHaveInit));
} while (this.eat(TokenType.COMMA));
return result;
}
parseVariableDeclarator(bindingPatternsMustHaveInit) {
let startState = this.startNode();
if (this.match(TokenType.LPAREN)) {
throw this.createUnexpected(this.lookahead);
}
let previousAllowIn = this.allowIn;
this.allowIn = true;
let binding = this.parseBindingTarget();
this.allowIn = previousAllowIn;
if (bindingPatternsMustHaveInit && binding.type !== 'BindingIdentifier' && !this.match(TokenType.ASSIGN)) {
this.expect(TokenType.ASSIGN);
}
let init = null;
if (this.eat(TokenType.ASSIGN)) {
init = this.parseAssignmentExpression();
}
return this.finishNode(new AST.VariableDeclarator({ binding, init }), startState);
}
isolateCoverGrammar(parser) {
let oldIsBindingElement = this.isBindingElement,
oldIsAssignmentTarget = this.isAssignmentTarget,
oldFirstExprError = this.firstExprError,
result;
this.isBindingElement = this.isAssignmentTarget = true;
this.firstExprError = null;
result = parser.call(this);
if (this.firstExprError !== null) {
throw this.firstExprError;
}
this.isBindingElement = oldIsBindingElement;
this.isAssignmentTarget = oldIsAssignmentTarget;
this.firstExprError = oldFirstExprError;
return result;
}
inheritCoverGrammar(parser) {
let oldIsBindingElement = this.isBindingElement,
oldIsAssignmentTarget = this.isAssignmentTarget,
oldFirstExprError = this.firstExprError,
result;
this.isBindingElement = this.isAssignmentTarget = true;
this.firstExprError = null;
result = parser.call(this);
this.isBindingElement = this.isBindingElement && oldIsBindingElement;
this.isAssignmentTarget = this.isAssignmentTarget && oldIsAssignmentTarget;
this.firstExprError = oldFirstExprError || this.firstExprError;
return result;
}
parseExpression() {
let startState = this.startNode();
let left = this.parseAssignmentExpression();
if (this.match(TokenType.COMMA)) {
while (!this.eof()) {
if (!this.match(TokenType.COMMA)) break;
this.lex();
let right = this.parseAssignmentExpression();
left = this.finishNode(new AST.BinaryExpression({ left, operator: ',', right }), startState);
}
}
return left;
}
finishArrowParams(head) {
let { params = null, rest = null } = head;
if (head.type !== ARROW_EXPRESSION_PARAMS) {
if (head.type === 'IdentifierExpression') {
params = [this.targetToBinding(this.transformDestructuring(head))];
} else {
throw this.createUnexpected(this.lookahead);
}
}
return this.copyNode(head, new AST.FormalParameters({ items: params, rest }));
}
parseArrowExpressionTail(params, isAsync, startState) {
this.expect(TokenType.ARROW);
let previousYield = this.allowYieldExpression;
let previousAwait = this.allowAwaitExpression;
let previousAwaitLocation = this.firstAwaitLocation;
this.allowYieldExpression = false;
this.allowAwaitExpression = isAsync;
this.firstAwaitLocation = null;
let body;
if (this.match(TokenType.LBRACE)) {
let previousAllowIn = this.allowIn;
this.allowIn = true;
body = this.parseFunctionBody();
this.allowIn = previousAllowIn;
} else {
body = this.parseAssignmentExpression();
}
this.allowYieldExpression = previousYield;
this.allowAwaitExpression = previousAwait;
this.firstAwaitLocation = previousAwaitLocation;
return this.finishNode(new AST.ArrowExpression({ isAsync, params, body }), startState);
}
parseAssignmentExpression() {
return this.isolateCoverGrammar(this.parseAssignmentExpressionOrTarget);
}
parseAssignmentExpressionOrTarget() {
let startState = this.startNode();
if (this.allowYieldExpression && this.match(TokenType.YIELD)) {
this.isBindingElement = this.isAssignmentTarget = false;
return this.parseYieldExpression();
}
let expr = this.parseConditionalExpression();
if (!this.hasLineTerminatorBeforeNext && this.match(TokenType.ARROW)) {
this.isBindingElement = this.isAssignmentTarget = false;
this.firstExprError = null;
let isAsync = expr.type === ARROW_EXPRESSION_PARAMS && expr.isAsync;
return this.parseArrowExpressionTail(this.finishArrowParams(expr), isAsync, startState);
}
let isAssignmentOperator = false;
let operator = this.lookahead;
switch (operator.type) {
case TokenType.ASSIGN_BIT_OR:
case TokenType.ASSIGN_BIT_XOR:
case TokenType.ASSIGN_BIT_AND:
case TokenType.ASSIGN_SHL:
case TokenType.ASSIGN_SHR:
case TokenType.ASSIGN_SHR_UNSIGNED:
case TokenType.ASSIGN_ADD:
case TokenType.ASSIGN_SUB:
case TokenType.ASSIGN_MUL:
case TokenType.ASSIGN_DIV:
case TokenType.ASSIGN_MOD:
case TokenType.ASSIGN_EXP:
isAssignmentOperator = true;
break;
}
if (isAssignmentOperator) {
if (!this.isAssignmentTarget || !isValidSimpleAssignmentTarget(expr)) {
throw this.createError(ErrorMessages.INVALID_LHS_IN_ASSIGNMENT);
}
expr = this.transformDestructuring(expr);
} else if (operator.type === TokenType.ASSIGN) {
if (!this.isAssignmentTarget) {
throw this.createError(ErrorMessages.INVALID_LHS_IN_ASSIGNMENT);
}
expr = this.transformDestructuring(expr);
} else {
return expr;
}
this.lex();
let rhs = this.parseAssignmentExpression();
this.firstExprError = null;
let node;
if (operator.type === TokenType.ASSIGN) {
node = new AST.AssignmentExpression({ binding: expr, expression: rhs });
} else {
node = new AST.CompoundAssignmentExpression({ binding: expr, operator: operator.type.name, expression: rhs });
this.isBindingElement = this.isAssignmentTarget = false;
}
return this.finishNode(node, startState);
}
targetToBinding(node) {
if (node === null) {
return null;
}
switch (node.type) {
case 'AssignmentTargetIdentifier':
return this.copyNode(node, new AST.BindingIdentifier({ name: node.name }));
case 'ArrayAssignmentTarget':
return this.copyNode(node, new AST.ArrayBinding({ elements: node.elements.map(e => this.targetToBinding(e)), rest: this.targetToBinding(node.rest) }));
case 'ObjectAssignmentTarget':
return this.copyNode(node, new AST.ObjectBinding({ properties: node.properties.map(p => this.targetToBinding(p)), rest: this.targetToBinding(node.rest) }));
case 'AssignmentTargetPropertyIdentifier':
return this.copyNode(node, new AST.BindingPropertyIdentifier({ binding: this.targetToBinding(node.binding), init: node.init }));
case 'AssignmentTargetPropertyProperty':
return this.copyNode(node, new AST.BindingPropertyProperty({ name: node.name, binding: this.targetToBinding(node.binding) }));
case 'AssignmentTargetWithDefault':
return this.copyNode(node, new AST.BindingWithDefault({ binding: this.targetToBinding(node.binding), init: node.init }));
}
// istanbul ignore next
throw new Error('Not reached');
}
transformDestructuring(node) {
switch (node.type) {
case 'DataProperty':
return this.copyNode(node, new AST.AssignmentTargetPropertyProperty({
name: node.name,
binding: this.transformDestructuringWithDefault(node.expression),
}));
case 'ShorthandProperty':
return this.copyNode(node, new AST.AssignmentTargetPropertyIdentifier({
binding: this.copyNode(node, new AST.AssignmentTargetIdentifier({ name: node.name.name })),
init: null,
}));
case 'ObjectExpression': {
let last = node.properties.length > 0 ? node.properties[node.properties.length - 1] : void 0;
if (last != null && last.type === 'SpreadProperty') {
return this.copyNode(node, new AST.ObjectAssignmentTarget({
properties: node.properties.slice(0, -1).map(e => e && this.transformDestructuringWithDefault(e)),
rest: this.transformDestructuring(last.expression),
}));
}
return this.copyNode(node, new AST.ObjectAssignmentTarget({
properties: node.properties.map(e => e && this.transformDestructuringWithDefault(e)),
rest: null,
}));
}
case 'ArrayExpression': {
let last = node.elements[node.elements.length - 1];
if (last != null && last.type === 'SpreadElement') {
return this.copyNode(node, new AST.ArrayAssignmentTarget({
elements: node.elements.slice(0, -1).map(e => e && this.transformDestructuringWithDefault(e)),
rest: this.copyNode(last.expression, this.transformDestructuring(last.expression)),
}));
}
return this.copyNode(node, new AST.ArrayAssignmentTarget({
elements: node.elements.map(e => e && this.transformDestructuringWithDefault(e)),
rest: null,
}));
}
case 'IdentifierExpression':
return this.copyNode(node, new AST.AssignmentTargetIdentifier({ name: node.name }));
case 'StaticPropertyName':
return this.copyNode(node, new AST.AssignmentTargetIdentifier({ name: node.value }));
case 'ComputedMemberExpression':
return this.copyNode(node, new AST.ComputedMemberAssignmentTarget({ object: node.object, expression: node.expression }));
case 'StaticMemberExpression':
return this.copyNode(node, new AST.StaticMemberAssignmentTarget({ object: node.object, property: node.property }));
case 'ArrayAssignmentTarget':
case 'ObjectAssignmentTarget':
case 'ComputedMemberAssignmentTarget':
case 'StaticMemberAssignmentTarget':
case 'AssignmentTargetIdentifier':
case 'AssignmentTargetPropertyIdentifier':
case 'AssignmentTargetPropertyProperty':
case 'AssignmentTargetWithDefault':
return node;
}
// istanbul ignore next
throw new Error('Not reached');
}
transformDestructuringWithDefault(node) {
switch (node.type) {
case 'AssignmentExpression':
return this.copyNode(node, new AST.AssignmentTargetWithDefault({
binding: this.transformDestructuring(node.binding),
init: node.expression,
}));
}
return this.transformDestructuring(node);
}
lookaheadAssignmentExpression() {
if (this.matchIdentifier()) {
return true;
}
switch (this.lookahead.type) {
case TokenType.ADD:
case TokenType.ASSIGN_DIV:
case TokenType.BIT_NOT:
case TokenType.CLASS:
case TokenType.DEC:
case TokenType.DELETE:
case TokenType.DIV:
case TokenType.FALSE:
case TokenType.FUNCTION:
case TokenType.INC:
case TokenType.LBRACE:
case TokenType.LBRACK:
case TokenType.LPAREN:
case TokenType.NEW:
case TokenType.NOT:
case TokenType.NULL:
case TokenType.NUMBER:
case TokenType.STRING:
case TokenType.SUB:
case TokenType.SUPER:
case TokenType.THIS:
case TokenType.TRUE:
case TokenType.TYPEOF:
case TokenType.VOID:
case TokenType.TEMPLATE:
return true;
}
return false;
}
parseYieldExpression() {
let startState = this.startNode();
this.lex();
if (this.hasLineTerminatorBeforeNext) {
return this.finishNode(new AST.YieldExpression({ expression: null }), startState);
}
let isGenerator = !!this.eat(TokenType.MUL);
let expr = null;
if (isGenerator || this.lookaheadAssignmentExpression()) {
expr = this.parseAssignmentExpression();
}
let ctor = isGenerator ? AST.YieldGeneratorExpression : AST.YieldExpression;
return this.finishNode(new ctor({ expression: expr }), startState);
}
parseConditionalExpression() {
let startState = this.startNode();
let test = this.parseBinaryExpression();
if (this.firstExprError) return test;
if (this.eat(TokenType.CONDITIONAL)) {
this.isBindingElement = this.isAssignmentTarget = false;
let previousAllowIn = this.allowIn;
this.allowIn = true;
let consequent = this.isolateCoverGrammar(this.parseAssignmentExpression);
this.allowIn = previousAllowIn;
this.expect(TokenType.COLON);
let alternate = this.isolateCoverGrammar(this.parseAssignmentExpression);
return this.finishNode(new AST.ConditionalExpression({ test, consequent, alternate }), startState);
}
return test;
}
isBinaryOperator(type) {
switch (type) {
case TokenType.OR:
case TokenType.AND:
case TokenType.BIT_OR:
case TokenType.BIT_XOR:
case TokenType.BIT_AND:
case TokenType.EQ:
case TokenType.NE:
case TokenType.EQ_STRICT:
case TokenType.NE_STRICT:
case TokenType.LT:
case TokenType.GT:
case TokenType.LTE:
case TokenType.GTE:
case TokenType.INSTANCEOF:
case TokenType.SHL:
case TokenType.SHR:
case TokenType.SHR_UNSIGNED:
case TokenType.ADD:
case TokenType.SUB:
case TokenType.MUL:
case TokenType.DIV:
case TokenType.MOD:
return true;
case TokenType.IN:
return this.allowIn;
default:
return false;
}
}
parseBinaryExpression() {
let startState = this.startNode();
let left = this.parseExponentiationExpression();
if (this.firstExprError) {
return left;
}
let operator = this.lookahead.type;
if (!this.isBinaryOperator(operator)) return left;
this.isBindingElement = this.isAssignmentTarget = false;
this.lex();
let stack = [];
stack.push({ startState, left, operator, precedence: BinaryPrecedence[operator.name] });
startState = this.startNode();
let right = this.isolateCoverGrammar(this.parseExponentiationExpression);
operator = this.lookahead.type;
while (this.isBinaryOperator(operator)) {
let precedence = BinaryPrecedence[operator.name];
// Reduce: make a binary expression from the three topmost entries.
while (stack.length && precedence <= stack[stack.length - 1].precedence) {
let stackItem = stack[stack.length - 1];
let stackOperator = stackItem.operator;
left = stackItem.left;
stack.pop();
startState = stackItem.startState;
right = this.finishNode(new AST.BinaryExpression({ left, operator: stackOperator.name, right }), startState);
}
this.lex();
stack.push({ startState, left: right, operator, precedence });
startState = this.startNode();
right = this.isolateCoverGrammar(this.parseExponentiationExpression);
operator = this.lookahead.type;
}
// Final reduce to clean-up the stack.
return stack.reduceRight((expr, stackItem) =>
this.finishNode(new AST.BinaryExpression({
left: stackItem.left,
operator: stackItem.operator.name,
right: expr,
}), stackItem.startState),
right);
}
parseExponentiationExpression() {
let startState = this.startNode();
let leftIsParenthesized = this.lookahead.type === TokenType.LPAREN;
let left = this.parseUnaryExpression();
if (this.lookahead.type !== TokenType.EXP) {
return left;
}
if (left.type === 'UnaryExpression' && !leftIsParenthesized) {
throw this.createError(ErrorMessages.INVALID_EXPONENTIATION_LHS);
}
this.lex();
this.isBindingElement = this.isAssignmentTarget = false;
let right = this.isolateCoverGrammar(this.parseExponentiationExpression);
return this.finishNode(new AST.BinaryExpression({ left, operator: '**', right }), startState);
}
parseUnaryExpression() {
if (this.lookahead.type.klass !== TokenClass.Punctuator && this.lookahead.type.klass !== TokenClass.Keyword) {
return this.parseUpdateExpression();
}
let startState = this.startNode();
if (this.allowAwaitExpression && this.eat(TokenType.AWAIT)) {
this.isBindingElement = this.isAssignmentTarget = false;
let expression = this.isolateCoverGrammar(this.parseUnaryExpression);
return this.finishNode(new AST.AwaitExpression({ expression }), startState);
}
let operator = this.lookahead;
if (!isPrefixOperator(operator)) {
return this.parseUpdateExpression();
}
this.lex();
this.isBindingElement = this.isAssignmentTarget = false;
let node;
if (isUpdateOperator(operator)) {
let operandStartLocation = this.getLocation();
let operand = this.isolateCoverGrammar(this.parseUnaryExpression);
if (!isValidSimpleAssignmentTarget(operand)) {
throw this.createErrorWithLocation(operandStartLocation, ErrorMessages.INVALID_UPDATE_OPERAND);
}
operand = this.transformDestructuring(operand);
node = new AST.UpdateExpression({ isPrefix: true, operator: operator.value, operand });
} else {
let operand = this.isolateCoverGrammar(this.parseUnaryExpression);
node = new AST.UnaryExpression({ operator: operator.value, operand });
}
return this.finishNode(node, startState);
}
parseUpdateExpression() {
let startLocation = this.getLocation();
let startState = this.startNode();
let operand = this.parseLeftHandSideExpression({ allowCall: true });
if (this.firstExprError || this.hasLineTerminatorBeforeNext) return operand;
let operator = this.lookahead;
if (!isUpdateOperator(operator)) return operand;
this.lex();
this.isBindingElement = this.isAssignmentTarget = false;
if (!isValidSimpleAssignmentTarget(operand)) {
throw this.createErrorWithLocation(startLocation, ErrorMessages.INVALID_UPDATE_OPERAND);
}
operand = this.transformDestructuring(operand);
return this.finishNode(new AST.UpdateExpression({ isPrefix: false, operator: operator.value, operand }), startState);
}
parseLeftHandSideExpression({ allowCall }) {
let startState = this.startNode();
let previousAllowIn = this.allowIn;
this.allowIn = true;
let expr, token = this.lookahead;
if (this.eat(TokenType.SUPER)) {
this.isBindingElement = false;
this.isAssignmentTarget = false;
expr = this.finishNode(new AST.Super, startState);
if (this.match(TokenType.LPAREN)) {
if (allowCall) {
expr = this.finishNode(new AST.CallExpression({
callee: expr,
arguments: this.parseArgumentList().args,
}), startState);
} else {
throw this.createUnexpected(token);
}
} else if (this.match(TokenType.LBRACK)) {
expr = this.finishNode(new AST.ComputedMemberExpression({
object: expr,
expression: this.parseComputedMember(),
}), startState);
this.isAssignmentTarget = true;
} else if (this.match(TokenType.PERIOD)) {
expr = this.finishNode(new AST.StaticMemberExpression({
object: expr,
property: this.parseStaticMember(),
}), startState);
this.isAssignmentTarget = true;
} else {
throw this.createUnexpected(token);
}
} else if (this.match(TokenType.NEW)) {
this.isBindingElement = this.isAssignmentTarget = false;
expr = this.parseNewExpression();
} else if (this.match(TokenType.ASYNC)) {
expr = this.parsePrimaryExpression();
// there's only three things this could be: an identifier, an async arrow, or an async function expression.
if (expr.type === 'IdentifierExpression' && allowCall && !this.hasLineTerminatorBeforeNext) {
if (this.matchIdentifier()) {
// `async [no lineterminator here] identifier` must be an async arrow
let afterAsyncStartState = this.startNode();
let previousAwait = this.allowAwaitExpression;
this.allowAwaitExpression = true;
let param = this.parseBindingIdentifier();
this.allowAwaitExpression = previousAwait;
this.ensureArrow();
return this.finishNode({
type: ARROW_EXPRESSION_PARAMS,
params: [param],
rest: null,
isAsync: true,
}, afterAsyncStartState);
}
if (this.match(TokenType.LPAREN)) {
// the maximally obnoxious case: `async (`
let afterAsyncStartState = this.startNode();
let previousAwaitLocation = this.firstAwaitLocation;
this.firstAwaitLocation = null;
let { args, locationFollowingFirstSpread } = this.parseArgumentList();
if (this.isBindingElement && !this.hasLineTerminatorBeforeNext && this.match(TokenType.ARROW)) {
if (locationFollowingFirstSpread !== null) {
throw this.createErrorWithLocation(locationFollowingFirstSpread, ErrorMessages.UNEXPECTED_TOKEN(','));
}
if (this.firstAwaitLocation !== null) {
throw this.createErrorWithLocation(this.firstAwaitLocation, ErrorMessages.NO_AWAIT_IN_ASYNC_PARAMS);
}
let rest = null;
if (args.length > 0 && args[args.length - 1].type === 'SpreadElement') {
rest = this.targetToBinding(this.transformDestructuringWithDefault(args[args.length - 1].expression));
if (rest.init != null) {
throw this.createError(ErrorMessages.UNEXPECTED_REST_PARAMETERS_INITIALIZATION);
}
args = args.slice(0, -1);
}
let params = args.map(arg => this.targetToBinding(this.transformDestructuringWithDefault(arg)));
return this.finishNode({
type: ARROW_EXPRESSION_PARAMS,
params,
rest,
isAsync: true,
}, afterAsyncStartState);
}
this.firstAwaitLocation = previousAwaitLocation || this.firstAwaitLocation;
// otherwise we've just taken the first iteration of the loop below
this.isBindingElement = this.isAssignmentTarget = false;
expr = this.finishNode(new AST.CallExpression({
callee: expr,
arguments: args,
}), startState);
}
}
} else {
expr = this.parsePrimaryExpression();
if (this.firstExprError) {
return expr;
}
}
while (true) {
if (allowCall && this.match(TokenType.LPAREN)) {
this.isBindingElement = this.isAssignmentTarget = false;
expr = this.finishNode(new AST.CallExpression({
callee: expr,
arguments: this.parseArgumentList().args,
}), startState);
} else if (this.match(TokenType.LBRACK)) {
this.isBindingElement = false;
this.isAssignmentTarget = true;
expr = this.finishNode(new AST.ComputedMemberExpression({
object: expr,
expression: this.parseComputedMember(),
}), startState);
} else if (this.match(TokenType.PERIOD)) {
this.isBindingElement = false;
this.isAssignmentTarget = true;
expr = this.finishNode(new AST.StaticMemberExpression({
object: expr,
property: this.parseStaticMember(),
}), startState);
} else if (this.match(TokenType.TEMPLATE)) {
this.isBindingElement = this.isAssignmentTarget = false;
expr = this.finishNode(new AST.TemplateExpression({
tag: expr,
elements: this.parseTemplateElements(),
}), startState);
} else {
break;
}
}
this.allowIn = previousAllowIn;
return expr;
}
parseTemplateElements() {
let startState = this.startNode();
let token = this.lookahead;
if (token.tail) {
this.lex();
return [this.finishNode(new AST.TemplateElement({ rawValue: token.slice.text.slice(1, -1) }), startState)];
}
let result = [
this.finishNode(new AST.TemplateElement({ rawValue: this.lex().slice.text.slice(1, -2) }), startState),
];
while (true) {
result.push(this.parseExpression());
if (!this.match(TokenType.RBRACE)) {
throw this.createILLEGAL();
}
this.index = this.startIndex;
this.line = this.startLine;
this.lineStart = this.startLineStart;
this.lookahead = this.scanTemplateElement();
startState = this.startNode();
token = this.lex();
if (token.tail) {
result.push(this.finishNode(new AST.TemplateElement({ rawValue: token.slice.text.slice(1, -1) }), startState));
return result;
}
result.push(this.finishNode(new AST.TemplateElement({ rawValue: token.slice.text.slice(1, -2) }), startState));
}
}
parseStaticMember() {
this.lex();
if (this.lookahead.type.klass.isIdentifierName) {
return this.lex().value;
}
throw this.createUnexpected(this.lookahead);
}
parseComputedMember() {
this.lex();
let expr = this.parseExpression();
this.expect(TokenType.RBRACK);
return expr;
}
parseNewExpression() {
let startState = this.startNode();
this.lex();
if (this.eat(TokenType.PERIOD)) {
this.expectContextualKeyword('target');
return this.finishNode(new AST.NewTargetExpression, startState);
}
let callee = this.isolateCoverGrammar(() => this.parseLeftHandSideExpression({ allowCall: false }));
return this.finishNode(new AST.NewExpression({
callee,
arguments: this.match(TokenType.LPAREN) ? this.parseArgumentList().args : [],
}), startState);
}
parseRegexFlags(flags) {
let isGlobal = false,
ignoreCase = false,
multiLine = false,
unicode = false,
sticky = false,
dotAll = false;
for (let i = 0; i < flags.length; ++i) {
let f = flags[i];
switch (f) {
case 'g':
if (isGlobal) {
throw this.createError('Duplicate regular expression flag \'g\'');
}
isGlobal = true;
break;
case 'i':
if (ignoreCase) {
throw this.createError('Duplicate regular expression flag \'i\'');
}
ignoreCase = true;
break;
case 'm':
if (multiLine) {
throw this.createError('Duplicate regular expression flag \'m\'');
}
multiLine = true;
break;
case 'u':
if (unicode) {
throw this.createError('Duplicate regular expression flag \'u\'');
}
unicode = true;
break;
case 'y':
if (sticky) {
throw this.createError('Duplicate regular expression flag \'y\'');
}
sticky = true;
break;
case 's':
if (dotAll) {
throw this.createError('Duplicate regular expression flag \'s\'');
}
dotAll = true;
break;
default:
throw this.createError(`Invalid regular expression flag '${f}'`);
}
}
return { global: isGlobal, ignoreCase, multiLine, unicode, sticky, dotAll };
}
parsePrimaryExpression() {
if (this.match(TokenType.LPAREN)) {
return this.parseGroupExpression();
}
let startState = this.startNode();
if (this.eat(TokenType.ASYNC)) {
if (!this.hasLineTerminatorBeforeNext && this.match(TokenType.FUNCTION)) {
this.isBindingElement = this.isAssignmentTarget = false;
return this.finishNode(this.parseFunction({ isExpr: true, inDefault: false, allowGenerator: true, isAsync: true }), startState);
}
return this.finishNode(new AST.IdentifierExpression({ name: 'async' }), startState);
}
if (this.matchIdentifier()) {
return this.finishNode(new AST.IdentifierExpression({ name: this.parseIdentifier() }), startState);
}
switch (this.lookahead.type) {
case TokenType.STRING:
this.isBindingElement = this.isAssignmentTarget = false;
return this.parseStringLiteral();
case TokenType.NUMBER:
this.isBindingElement = this.isAssignmentTarget = false;
return this.parseNumericLiteral();
case TokenType.THIS:
this.lex();
this.isBindingElement = this.isAssignmentTarget = false;
return this.finishNode(new AST.ThisExpression, startState);
case TokenType.FUNCTION:
this.isBindingElement = this.isAssignmentTarget = false;
return this.finishNode(this.parseFunction({ isExpr: true, inDefault: false, allowGenerator: true, isAsync: false }), startState);
case TokenType.TRUE:
this.lex();
this.isBindingElement = this.isAssignmentTarget = false;
return this.finishNode(new AST.LiteralBooleanExpression({ value: true }), startState);
case TokenType.FALSE:
this.lex();
this.isBindingElement = this.isAssignmentTarget = false;
return this.finishNode(new AST.LiteralBooleanExpression({ value: false }), startState);
case TokenType.NULL:
this.lex();
this.isBindingElement = this.isAssignmentTarget = false;
return this.finishNode(new AST.LiteralNullExpression, startState);
case TokenType.LBRACK:
return this.parseArrayExpression();
case TokenType.LBRACE:
return this.parseObjectExpression();
case TokenType.TEMPLATE:
this.isBindingElement = this.isAssignmentTarget = false;
return this.finishNode(new AST.TemplateExpression({ tag: null, elements: this.parseTemplateElements() }), startState);
case TokenType.DIV:
case TokenType.ASSIGN_DIV: {
this.isBindingElement = this.isAssignmentTarget = false;
this.lookahead = this.scanRegExp(this.match(TokenType.DIV) ? '/' : '/=');
let token = this.lex();
let lastSlash = token.value.lastIndexOf('/');
let pattern = token.value.slice(1, lastSlash);
let flags = token.value.slice(lastSlash + 1);
let ctorArgs = this.parseRegexFlags(flags);
if (!acceptRegex(pattern, ctorArgs)) {
throw this.createError(ErrorMessages.INVALID_REGEX);
}
ctorArgs.pattern = pattern;
return this.finishNode(new AST.LiteralRegExpExpression(ctorArgs), startState);
}
case TokenType.CLASS:
this.isBindingElement = this.isAssignmentTarget = false;
return this.parseClass({ isExpr: true, inDefault: false });
default:
throw this.createUnexpected(this.lookahead);
}
}
parseNumericLiteral() {
let startLocation = this.getLocation();
let startState = this.startNode();
let token = this.lex();
if (token.octal && this.strict) {
if (token.noctal) {
throw this.createErrorWithLocation(startLocation, 'Unexpected noctal integer literal');
} else {
throw this.createErrorWithLocation(startLocation, 'Unexpected legacy octal integer literal');
}
}
let node = token.value === 1 / 0
? new AST.LiteralInfinityExpression
: new AST.LiteralNumericExpression({ value: token.value });
return this.finishNode(node, startState);
}
parseStringLiteral() {
let startLocation = this.getLocation();
let startState = this.startNode();
let token = this.lex();
if (token.octal != null && this.strict) {
throw this.createErrorWithLocation(startLocation, 'Unexpected legacy octal escape sequence: \\' + token.octal);
}
return this.finishNode(new AST.LiteralStringExpression({ value: token.str }), startState);
}
parseIdentifierName() {
if (this.lookahead.type.klass.isIdentifierName) {
return this.lex().value;
}
throw this.createUnexpected(this.lookahead);
}
parseBindingIdentifier() {
let startState = this.startNode();
return this.finishNode(new AST.BindingIdentifier({ name: this.parseIdentifier() }), startState);
}
parseIdentifier() {
if (this.lookahead.value === 'yield' && this.allowYieldExpression) {
throw this.createError(ErrorMessages.ILLEGAL_YIELD_IDENTIFIER);
}
if (this.lookahead.value === 'await' && this.allowAwaitExpression) {
throw this.createError(ErrorMessages.ILLEGAL_AWAIT_IDENTIFIER);
}
if (this.matchIdentifier()) {
return this.lex().value;
}
throw this.createUnexpected(this.lookahead);
}
parseArgumentList() {
this.lex();
let args = this.parseArguments();
this.expect(TokenType.RPAREN);
return args;
}
parseArguments() {
let args = [];
let locationFollowingFirstSpread = null;
while (!this.match(TokenType.RPAREN)) {
let arg;
let startState = this.startNode();
if (this.eat(TokenType.ELLIPSIS)) {
arg = this.finishNode(new AST.SpreadElement({ expression: this.inheritCoverGrammar(this.parseAssignmentExpressionOrTarget) }), startState);
if (locationFollowingFirstSpread === null) {
args.push(arg);
if (this.match(TokenType.RPAREN)) {
break;
}
locationFollowingFirstSpread = this.getLocation();
this.expect(TokenType.COMMA);
continue;
}
} else {
arg = this.inheritCoverGrammar(this.parseAssignmentExpressionOrTarget);
}
args.push(arg);
if (this.match(TokenType.RPAREN)) {
break;
}
this.expect(TokenType.COMMA);
}
return { args, locationFollowingFirstSpread };
}
// 11.2 Left-Hand-Side Expressions;
ensureArrow() {
if (this.hasLineTerminatorBeforeNext) {
throw this.createError(ErrorMessages.UNEXPECTED_LINE_TERMINATOR);
}
if (!this.match(TokenType.ARROW)) {
this.expect(TokenType.ARROW);
}
}
parseGroupExpression() {
// At this point, we need to parse 3 things:
// 1. Group expression
// 2. Assignment target of assignment expression
// 3. Parameter list of arrow function
let rest = null;
let preParenStartState = this.startNode();
let start = this.expect(TokenType.LPAREN);
let postParenStartState = this.startNode();
if (this.match(TokenType.RPAREN)) {
this.lex();
let paramsNode = this.finishNode({
type: ARROW_EXPRESSION_PARAMS,
params: [],
rest: null,
isAsync: false,
}, preParenStartState);
this.ensureArrow();
this.isBindingElement = this.isAssignmentTarget = false;
return paramsNode;
} else if (this.eat(TokenType.ELLIPSIS)) {
rest = this.parseBindingTarget();
if (this.match(TokenType.ASSIGN)) {
throw this.createError(ErrorMessages.INVALID_REST_PARAMETERS_INITIALIZATION);
}
if (this.match(TokenType.COMMA)) {
throw this.createError(ErrorMessages.INVALID_LAST_REST_PARAMETER);
}
this.expect(TokenType.RPAREN);
let paramsNode = this.finishNode({
type: ARROW_EXPRESSION_PARAMS,
params: [],
rest,
isAsync: false,
}, preParenStartState);
this.ensureArrow();
this.isBindingElement = this.isAssignmentTarget = false;
return paramsNode;
}
let group = this.inheritCoverGrammar(this.parseAssignmentExpressionOrTarget);
let params = this.isBindingElement ? [this.targetToBinding(this.transformDestructuringWithDefault(group))] : null;
while (this.eat(TokenType.COMMA)) {
if (this.match(TokenType.RPAREN)) {
if (!this.isBindingElement) {
throw this.createUnexpected(this.lookahead);
}
this.firstExprError = this.firstExprError || this.createUnexpected(this.lookahead);
group = null;
break;
}
this.isAssignmentTarget = false;
if (this.match(TokenType.ELLIPSIS)) {
if (!this.isBindingElement) {
throw this.createUnexpected(this.lookahead);
}
this.lex();
rest = this.parseBindingTarget();
if (this.match(TokenType.ASSIGN)) {
throw this.createError(ErrorMessages.INVALID_REST_PARAMETERS_INITIALIZATION);
}
if (this.match(TokenType.COMMA)) {
throw this.createError(ErrorMessages.INVALID_LAST_REST_PARAMETER);
}
break;
}
if (group) {
// Can be either binding element or assignment target.
let expr = this.inheritCoverGrammar(this.parseAssignmentExpressionOrTarget);
if (this.isBindingElement) {
params.push(this.targetToBinding(this.transformDestructuringWithDefault(expr)));
} else {
params = null;
}
if (this.firstExprError) {
group = null;
} else {
group = this.finishNode(new AST.BinaryExpression({
left: group,
operator: ',',
right: expr,
}), postParenStartState);
}
} else {
// Can be only binding elements.
let binding = this.parseBindingElement();
params.push(binding);
}
}
this.expect(TokenType.RPAREN);
if (!this.hasLineTerminatorBeforeNext && this.match(TokenType.ARROW)) {
if (!this.isBindingElement) {
throw this.createErrorWithLocation(start, ErrorMessages.ILLEGAL_ARROW_FUNCTION_PARAMS);
}
this.isBindingElement = false;
return this.finishNode({
type: ARROW_EXPRESSION_PARAMS,
params,
rest,
isAsync: false,
}, preParenStartState);
}
// Ensure assignment pattern:
if (rest) {
this.ensureArrow();
}
this.isBindingElement = false;
if (!isValidSimpleAssignmentTarget(group)) {
this.isAssignmentTarget = false;
}
return group;
}
parseArrayExpression() {
let startLocation = this.getLocation();
let startState = this.startNode();
this.lex();
let exprs = [];
let rest = null;
while (true) {
if (this.match(TokenType.RBRACK)) {
break;
}
if (this.eat(TokenType.COMMA)) {
exprs.push(null);
} else {
let elementStartState = this.startNode();
let expr;
if (this.eat(TokenType.ELLIPSIS)) {
// Spread/Rest element
expr = this.inheritCoverGrammar(this.parseAssignmentExpressionOrTarget);
if (!this.isAssignmentTarget && this.firstExprError) {
throw this.firstExprError;
}
if (expr.type === 'ArrayAssignmentTarget' || expr.type === 'ObjectAssignmentTarget') {
rest = expr;
break;
}
if (expr.type !== 'ArrayExpression' && expr.type !== 'ObjectExpression' && !isValidSimpleAssignmentTarget(expr)) {
this.isBindingElement = this.isAssignmentTarget = false;
}
expr = this.finishNode(new AST.SpreadElement({ expression: expr }), elementStartState);
if (!this.match(TokenType.RBRACK)) {
this.isBindingElement = this.isAssignmentTarget = false;
}
} else {
expr = this.inheritCoverGrammar(this.parseAssignmentExpressionOrTarget);
if (!this.isAssignmentTarget && this.firstExprError) {
throw this.firstExprError;
}
}
exprs.push(expr);
if (!this.match(TokenType.RBRACK)) {
this.expect(TokenType.COMMA);
}
}
}
if (rest && this.match(TokenType.COMMA)) {
throw this.createErrorWithLocation(startLocation, ErrorMessages.UNEXPECTED_COMMA_AFTER_REST);
}
this.expect(TokenType.RBRACK);
if (rest) {
// No need to check isAssignmentTarget: the only way to have something we know is a rest element is if we have ...Object/ArrayAssignmentTarget, which implies we have a firstExprError; as such, if isAssignmentTarget were false, we'd've thrown above before setting rest.
return this.finishNode(new AST.ArrayAssignmentTarget({
elements: exprs.map(e => e && this.transformDestructuringWithDefault(e)),
rest,
}), startState);
} else if (this.firstExprError) {
let last = exprs[exprs.length - 1];
if (last != null && last.type === 'SpreadElement') {
return this.finishNode(new AST.ArrayAssignmentTarget({
elements: exprs.slice(0, -1).map(e => e && this.transformDestructuringWithDefault(e)),
rest: this.transformDestructuring(last.expression),
}), startState);
}
return this.finishNode(new AST.ArrayAssignmentTarget({
elements: exprs.map(e => e && this.transformDestructuringWithDefault(e)),
rest: null,
}), startState);
}
return this.finishNode(new AST.ArrayExpression({ elements: exprs }), startState);
}
parseObjectExpression() {
let startState = this.startNode();
this.lex();
let properties = [];
while (!this.match(TokenType.RBRACE)) {
let isSpreadProperty = false;
if (this.match(TokenType.ELLIPSIS)) {
isSpreadProperty = true;
let spreadPropertyOrAssignmentTarget = this.parseSpreadPropertyDefinition();
properties.push(spreadPropertyOrAssignmentTarget);
} else {
let property = this.inheritCoverGrammar(this.parsePropertyDefinition);
properties.push(property);
}
if (!this.match(TokenType.RBRACE)) {
this.expect(TokenType.COMMA);
if (isSpreadProperty) {
this.isBindingElement = this.isAssignmentTarget = false;
}
}
}
this.expect(TokenType.RBRACE);
if (this.firstExprError) {
if (!this.isAssignmentTarget) {
throw this.createError(ErrorMessages.INVALID_LHS_IN_BINDING);
}
let last = properties[properties.length - 1];
if (last != null && last.type === 'SpreadProperty') {
return this.finishNode(new AST.ObjectAssignmentTarget({
properties: properties.slice(0, -1).map(p => this.transformDestructuringWithDefault(p)),
rest: this.transformDestructuring(last.expression),
}), startState);
}
return this.finishNode(new AST.ObjectAssignmentTarget({ properties: properties.map(p => this.transformDestructuringWithDefault(p)), rest: null }), startState);
}
return this.finishNode(new AST.ObjectExpression({ properties }), startState);
}
parseSpreadPropertyDefinition() {
let startState = this.startNode();
this.expect(TokenType.ELLIPSIS);
let expression = this.parseAssignmentExpression();
if (!isValidSimpleAssignmentTarget(expression)) {
this.isBindingElement = this.isAssignmentTarget = false;
} else if (expression.type !== 'IdentifierExpression') {
this.isBindingElement = false;
}
return this.finishNode(new AST.SpreadProperty({ expression }), startState);
}
parsePropertyDefinition() {
let startLocation = this.getLocation();
let startState = this.startNode();
let token = this.lookahead;
let { methodOrKey, kind } = this.parseMethodDefinition();
switch (kind) {
case 'method':
this.isBindingElement = this.isAssignmentTarget = false;
return methodOrKey;
case 'identifier':
if (token.value === 'await' && this.firstAwaitLocation == null) {
this.firstAwaitLocation = this.getLocation();
}
if (this.eat(TokenType.ASSIGN)) {
if (this.allowYieldExpression && token.value === 'yield') {
throw this.createError(ErrorMessages.ILLEGAL_YIELD_IDENTIFIER);
}
if (this.allowAwaitExpression && token.value === 'await') {
throw this.createError(ErrorMessages.ILLEGAL_AWAIT_IDENTIFIER);
}
// CoverInitializedName
let init = this.isolateCoverGrammar(this.parseAssignmentExpression);
this.firstExprError = this.createErrorWithLocation(startLocation, ErrorMessages.ILLEGAL_PROPERTY);
return this.finishNode(new AST.AssignmentTargetPropertyIdentifier({
binding: this.transformDestructuring(methodOrKey),
init,
}), startState);
} else if (!this.match(TokenType.COLON)) {
if (this.allowYieldExpression && token.value === 'yield') {
throw this.createError(ErrorMessages.ILLEGAL_YIELD_IDENTIFIER);
}
if (this.allowAwaitExpression && token.value === 'await') {
throw this.createError(ErrorMessages.ILLEGAL_AWAIT_IDENTIFIER);
}
if (token.type === TokenType.IDENTIFIER || token.value === 'let' || token.value === 'yield' || token.value === 'async' || token.value === 'await') {
return this.finishNode(new AST.ShorthandProperty({ name: this.finishNode(new AST.IdentifierExpression({ name: methodOrKey.value }), startState) }), startState);
}
throw this.createUnexpected(token);
}
}
// property
this.expect(TokenType.COLON);
let expr = this.inheritCoverGrammar(this.parseAssignmentExpressionOrTarget);
if (this.firstExprError) {
return this.finishNode(new AST.AssignmentTargetPropertyProperty({ name: methodOrKey, binding: expr }), startState);
}
return this.finishNode(new AST.DataProperty({ name: methodOrKey, expression: expr }), startState);
}
parsePropertyName() {
// PropertyName[Yield,GeneratorParameter]:
let token = this.lookahead;
let startState = this.startNode();
if (this.eof()) {
throw this.createUnexpected(token);
}
switch (token.type) {
case TokenType.STRING:
return {
name: this.finishNode(new AST.StaticPropertyName({
value: this.parseStringLiteral().value,
}), startState),
binding: null,
};
case TokenType.NUMBER: {
let numLiteral = this.parseNumericLiteral();
return {
name: this.finishNode(new AST.StaticPropertyName({
value: `${numLiteral.type === 'LiteralInfinityExpression' ? 1 / 0 : numLiteral.value}`,
}), startState),
binding: null,
};
}
case TokenType.LBRACK: {
this.lex();
let expr = this.parseAssignmentExpression();
this.expect(TokenType.RBRACK);
return { name: this.finishNode(new AST.ComputedPropertyName({ expression: expr }), startState), binding: null };
}
}
let name = this.parseIdentifierName();
return {
name: this.finishNode(new AST.StaticPropertyName({ value: name }), startState),
binding: this.finishNode(new AST.BindingIdentifier({ name }), startState),
};
}
/**
* Test if lookahead can be the beginning of a `PropertyName`.
* @returns {boolean}
*/
lookaheadPropertyName() {
switch (this.lookahead.type) {
case TokenType.NUMBER:
case TokenType.STRING:
case TokenType.LBRACK:
return true;
default:
return this.lookahead.type.klass.isIdentifierName;
}
}
// eslint-disable-next-line valid-jsdoc
/**
* Try to parse a method definition.
*
* If it turns out to be one of:
* * `IdentifierReference`
* * `CoverInitializedName` (`IdentifierReference "=" AssignmentExpression`)
* * `PropertyName : AssignmentExpression`
* The parser will stop at the end of the leading `Identifier` or `PropertyName` and return it.
*
* @returns {{methodOrKey: (Method|PropertyName), kind: string}}
*/
parseMethodDefinition() {
let token = this.lookahead;
let startState = this.startNode();
let preAsyncTokenState = this.saveLexerState();
let isAsync = !!this.eat(TokenType.ASYNC);
if (isAsync && this.hasLineTerminatorBeforeNext) {
isAsync = false;
this.restoreLexerState(preAsyncTokenState);
}
let isGenerator = !!this.eat(TokenType.MUL);
if (isAsync && !this.lookaheadPropertyName()) {
isAsync = false;
isGenerator = false;
this.restoreLexerState(preAsyncTokenState);
}
let { name } = this.parsePropertyName();
if (!isGenerator && !isAsync) {
if (token.type === TokenType.IDENTIFIER && token.value.length === 3) {
// Property Assignment: Getter and Setter.
if (token.value === 'get' && this.lookaheadPropertyName() && !token.escaped) {
({ name } = this.parsePropertyName());
this.expect(TokenType.LPAREN);
this.expect(TokenType.RPAREN);
let previousYield = this.allowYieldExpression;
let previousAwait = this.allowAwaitExpression;
let previousAwaitLocation = this.firstAwaitLocation;
this.allowYieldExpression = false;
this.allowAwaitExpression = false;
this.firstAwaitLocation = null;
let body = this.parseFunctionBody();
this.allowYieldExpression = previousYield;
this.allowAwaitExpression = previousAwait;
this.firstAwaitLocation = previousAwaitLocation;
return {
methodOrKey: this.finishNode(new AST.Getter({ name, body }), startState),
kind: 'method',
};
} else if (token.value === 'set' && this.lookaheadPropertyName() && !token.escaped) {
({ name } = this.parsePropertyName());
this.expect(TokenType.LPAREN);
let previousYield = this.allowYieldExpression;
let previousAwait = this.allowAwaitExpression;
let previousAwaitLocation = this.firstAwaitLocation;
this.allowYieldExpression = false;
this.allowAwaitExpression = false;
this.firstAwaitLocation = null;
let param = this.parseBindingElement();
this.expect(TokenType.RPAREN);
let body = this.parseFunctionBody();
this.allowYieldExpression = previousYield;
this.allowAwaitExpression = previousAwait;
this.firstAwaitLocation = previousAwaitLocation;
return {
methodOrKey: this.finishNode(new AST.Setter({ name, param, body }), startState),
kind: 'method',
};
}
}
}
if (isAsync) {
let previousYield = this.allowYieldExpression;
let previousAwait = this.allowAwaitExpression;
this.allowYieldExpression = isGenerator;
this.allowAwaitExpression = true;
let params = this.parseParams();
this.allowYieldExpression = isGenerator;
this.allowAwaitExpression = true;
let body = this.parseFunctionBody();
this.allowYieldExpression = previousYield;
this.allowAwaitExpression = previousAwait;
return {
methodOrKey: this.finishNode(new AST.Method({ isAsync, isGenerator, name, params, body }), startState),
kind: 'method',
};
}
if (this.match(TokenType.LPAREN)) {
let previousYield = this.allowYieldExpression;
let previousAwait = this.allowAwaitExpression;
let previousAwaitLocation = this.firstAwaitLocation;
this.allowYieldExpression = isGenerator;
this.allowAwaitExpression = false;
this.firstAwaitLocation = null;
let params = this.parseParams();
let body = this.parseFunctionBody();
this.allowYieldExpression = previousYield;
this.allowAwaitExpression = previousAwait;
this.firstAwaitLocation = previousAwaitLocation;
return {
methodOrKey: this.finishNode(new AST.Method({ isAsync, isGenerator, name, params, body }), startState),
kind: 'method',
};
}
if (isGenerator && this.match(TokenType.COLON)) {
throw this.createUnexpected(this.lookahead);
}
return {
methodOrKey: name,
kind: token.type.klass.isIdentifierName ? 'identifier' : 'property',
escaped: token.escaped,
};
}
parseClass({ isExpr, inDefault }) {
let startState = this.startNode();
this.lex();
let name = null;
let heritage = null;
if (this.matchIdentifier()) {
name = this.parseBindingIdentifier();
} else if (!isExpr) {
if (inDefault) {
name = new AST.BindingIdentifier({ name: '*default*' });
} else {
throw this.createUnexpected(this.lookahead);
}
}
if (this.eat(TokenType.EXTENDS)) {
heritage = this.isolateCoverGrammar(() => this.parseLeftHandSideExpression({ allowCall: true }));
}
this.expect(TokenType.LBRACE);
let elements = [];
while (!this.eat(TokenType.RBRACE)) {
if (this.eat(TokenType.SEMICOLON)) {
continue;
}
let isStatic = false;
let classElementStart = this.startNode();
let { methodOrKey, kind, escaped } = this.parseMethodDefinition();
if (kind === 'identifier' && methodOrKey.value === 'static' && !escaped) {
isStatic = true;
({ methodOrKey, kind } = this.parseMethodDefinition());
}
if (kind === 'method') {
elements.push(this.finishNode(new AST.ClassElement({ isStatic, method: methodOrKey }), classElementStart));
} else {
throw this.createError('Only methods are allowed in classes');
}
}
return this.finishNode(new (isExpr ? AST.ClassExpression : AST.ClassDeclaration)({ name, super: heritage, elements }), startState);
}
parseFunction({ isExpr, inDefault, allowGenerator, isAsync, startState = this.startNode() }) {
this.lex();
let name = null;
let isGenerator = allowGenerator && !!this.eat(TokenType.MUL);
let previousYield = this.allowYieldExpression;
let previousAwait = this.allowAwaitExpression;
let previousAwaitLocation = this.firstAwaitLocation;
if (isExpr) {
this.allowYieldExpression = isGenerator;
this.allowAwaitExpression = isAsync;
}
if (!this.match(TokenType.LPAREN)) {
name = this.parseBindingIdentifier();
} else if (!isExpr) {
if (inDefault) {
name = new AST.BindingIdentifier({ name: '*default*' });
} else {
throw this.createUnexpected(this.lookahead);
}
}
this.allowYieldExpression = isGenerator;
this.allowAwaitExpression = isAsync;
this.firstAwaitLocation = null;
let params = this.parseParams();
let body = this.parseFunctionBody();
this.allowYieldExpression = previousYield;
this.allowAwaitExpression = previousAwait;
this.firstAwaitLocation = previousAwaitLocation;
return this.finishNode(new (isExpr ? AST.FunctionExpression : AST.FunctionDeclaration)({ isAsync, isGenerator, name, params, body }), startState);
}
parseArrayBinding() {
let startState = this.startNode();
this.expect(TokenType.LBRACK);
let elements = [], rest = null;
while (true) {
if (this.match(TokenType.RBRACK)) {
break;
}
let el;
if (this.eat(TokenType.COMMA)) {
el = null;
} else {
if (this.eat(TokenType.ELLIPSIS)) {
rest = this.parseBindingTarget();
break;
} else {
el = this.parseBindingElement();
}
if (!this.match(TokenType.RBRACK)) {
this.expect(TokenType.COMMA);
}
}
elements.push(el);
}
this.expect(TokenType.RBRACK);
return this.finishNode(new AST.ArrayBinding({ elements, rest }), startState);
}
parseBindingProperty() {
let startState = this.startNode();
let isIdentifier = this.matchIdentifier();
let token = this.lookahead;
let { name, binding } = this.parsePropertyName();
if (isIdentifier && name.type === 'StaticPropertyName') {
if (!this.match(TokenType.COLON)) {
if (this.allowYieldExpression && token.value === 'yield') {
throw this.createError(ErrorMessages.ILLEGAL_YIELD_IDENTIFIER);
}
if (this.allowAwaitExpression && token.value === 'await') {
throw this.createError(ErrorMessages.ILLEGAL_AWAIT_IDENTIFIER);
}
let defaultValue = null;
if (this.eat(TokenType.ASSIGN)) {
defaultValue = this.parseAssignmentExpression();
}
return this.finishNode(new AST.BindingPropertyIdentifier({
binding,
init: defaultValue,
}), startState);
}
}
this.expect(TokenType.COLON);
binding = this.parseBindingElement();
return this.finishNode(new AST.BindingPropertyProperty({ name, binding }), startState);
}
parseObjectBinding() {
let startState = this.startNode();
this.expect(TokenType.LBRACE);
let properties = [];
let rest = null;
while (!this.match(TokenType.RBRACE)) {
if (this.eat(TokenType.ELLIPSIS)) {
rest = this.parseBindingIdentifier();
break;
}
properties.push(this.parseBindingProperty());
if (!this.match(TokenType.RBRACE)) {
this.expect(TokenType.COMMA);
}
}
this.expect(TokenType.RBRACE);
return this.finishNode(new AST.ObjectBinding({ properties, rest }), startState);
}
parseBindingTarget() {
if (this.matchIdentifier()) {
return this.parseBindingIdentifier();
}
switch (this.lookahead.type) {
case TokenType.LBRACK:
return this.parseArrayBinding();
case TokenType.LBRACE:
return this.parseObjectBinding();
}
throw this.createUnexpected(this.lookahead);
}
parseBindingElement() {
let startState = this.startNode();
let binding = this.parseBindingTarget();
if (this.eat(TokenType.ASSIGN)) {
let init = this.parseAssignmentExpression();
binding = this.finishNode(new AST.BindingWithDefault({ binding, init }), startState);
}
return binding;
}
parseParam() {
let previousInParameter = this.inParameter;
this.inParameter = true;
let param = this.parseBindingElement();
this.inParameter = previousInParameter;
return param;
}
parseParams() {
let startState = this.startNode();
this.expect(TokenType.LPAREN);
let items = [], rest = null;
while (!this.match(TokenType.RPAREN)) {
if (this.eat(TokenType.ELLIPSIS)) {
rest = this.parseBindingTarget();
if (this.lookahead.type === TokenType.ASSIGN) {
throw this.createError(ErrorMessages.UNEXPECTED_REST_PARAMETERS_INITIALIZATION);
}
if (this.match(TokenType.COMMA)) {
throw this.createError(ErrorMessages.UNEXPECTED_COMMA_AFTER_REST);
}
break;
}
items.push(this.parseParam());
if (this.match(TokenType.RPAREN)) break;
this.expect(TokenType.COMMA);
}
this.expect(TokenType.RPAREN);
return this.finishNode(new AST.FormalParameters({ items, rest }), startState);
}
}
module.exports = { GenericParser };