lib/Parser/JSParserImpl.cpp (5,371 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "JSParserImpl.h" #include "hermes/AST/ESTreeJSONDumper.h" #include "hermes/Support/PerfSection.h" #include "llvh/Support/SaveAndRestore.h" using llvh::cast; using llvh::dyn_cast; using llvh::isa; namespace hermes { namespace parser { namespace detail { JSParserImpl::JSParserImpl( Context &context, std::unique_ptr<llvh::MemoryBuffer> input) : context_(context), sm_(context.getSourceErrorManager()), lexer_( std::move(input), context.getSourceErrorManager(), context.getAllocator(), &context.getStringTable(), context.isStrictMode()), pass_(FullParse) { initializeIdentifiers(); } JSParserImpl::JSParserImpl(Context &context, uint32_t bufferId, ParserPass pass) : context_(context), sm_(context.getSourceErrorManager()), lexer_( bufferId, context.getSourceErrorManager(), context.getAllocator(), &context.getStringTable(), context.isStrictMode()), pass_(pass) { preParsed_ = context.getPreParsedBufferInfo(bufferId); initializeIdentifiers(); } void JSParserImpl::initializeIdentifiers() { getIdent_ = lexer_.getIdentifier("get"); setIdent_ = lexer_.getIdentifier("set"); initIdent_ = lexer_.getIdentifier("init"); useStrictIdent_ = lexer_.getIdentifier("use strict"); showSourceIdent_ = lexer_.getIdentifier("show source"); hideSourceIdent_ = lexer_.getIdentifier("hide source"); sensitiveIdent_ = lexer_.getIdentifier("sensitive"); useStaticBuiltinIdent_ = lexer_.getIdentifier("use static builtin"); letIdent_ = lexer_.getIdentifier("let"); ofIdent_ = lexer_.getIdentifier("of"); fromIdent_ = lexer_.getIdentifier("from"); asIdent_ = lexer_.getIdentifier("as"); implementsIdent_ = lexer_.getIdentifier("implements"); interfaceIdent_ = lexer_.getIdentifier("interface"); packageIdent_ = lexer_.getIdentifier("package"); privateIdent_ = lexer_.getIdentifier("private"); protectedIdent_ = lexer_.getIdentifier("protected"); publicIdent_ = lexer_.getIdentifier("public"); staticIdent_ = lexer_.getIdentifier("static"); methodIdent_ = lexer_.getIdentifier("method"); constructorIdent_ = lexer_.getIdentifier("constructor"); yieldIdent_ = lexer_.getIdentifier("yield"); newIdent_ = lexer_.getIdentifier("new"); targetIdent_ = lexer_.getIdentifier("target"); valueIdent_ = lexer_.getIdentifier("value"); typeIdent_ = lexer_.getIdentifier("type"); asyncIdent_ = lexer_.getIdentifier("async"); awaitIdent_ = lexer_.getIdentifier("await"); assertIdent_ = lexer_.getIdentifier("assert"); #if HERMES_PARSE_FLOW typeofIdent_ = lexer_.getIdentifier("typeof"); declareIdent_ = lexer_.getIdentifier("declare"); protoIdent_ = lexer_.getIdentifier("proto"); opaqueIdent_ = lexer_.getIdentifier("opaque"); plusIdent_ = lexer_.getIdentifier("plus"); minusIdent_ = lexer_.getIdentifier("minus"); moduleIdent_ = lexer_.getIdentifier("module"); exportsIdent_ = lexer_.getIdentifier("exports"); esIdent_ = lexer_.getIdentifier("ES"); commonJSIdent_ = lexer_.getIdentifier("CommonJS"); mixinsIdent_ = lexer_.getIdentifier("mixins"); thisIdent_ = lexer_.getIdentifier("this"); anyIdent_ = lexer_.getIdentifier("any"); mixedIdent_ = lexer_.getIdentifier("mixed"); emptyIdent_ = lexer_.getIdentifier("empty"); booleanIdent_ = lexer_.getIdentifier("boolean"); boolIdent_ = lexer_.getIdentifier("bool"); numberIdent_ = lexer_.getIdentifier("number"); stringIdent_ = lexer_.getIdentifier("string"); voidIdent_ = lexer_.getIdentifier("void"); nullIdent_ = lexer_.getIdentifier("null"); symbolIdent_ = lexer_.getIdentifier("symbol"); checksIdent_ = lexer_.getIdentifier("%checks"); #endif #if HERMES_PARSE_TS namespaceIdent_ = lexer_.getIdentifier("namespace"); readonlyIdent_ = lexer_.getIdentifier("readonly"); isIdent_ = lexer_.getIdentifier("is"); #endif // Generate the string representation of all tokens. for (unsigned i = 0; i != NUM_JS_TOKENS; ++i) tokenIdent_[i] = lexer_.getIdentifier(tokenKindStr((TokenKind)i)); } Optional<ESTree::ProgramNode *> JSParserImpl::parse() { PerfSection parsing("Parsing JavaScript"); tok_ = lexer_.advance(); auto res = parseProgram(); if (!res) return None; if (lexer_.getSourceMgr().getErrorCount() != 0) return None; return res.getValue(); } void JSParserImpl::errorExpected( ArrayRef<TokenKind> toks, const char *where, const char *what, SMLoc whatLoc) { llvh::SmallString<4> str; llvh::raw_svector_ostream ss{str}; for (unsigned i = 0; i < toks.size(); ++i) { // Insert a separator after the first token. if (i > 0) { // Use " or " instead of ", " before the last token. if (i == toks.size() - 1) ss << " or "; else ss << ", "; } ss << "'" << tokenKindStr(toks[i]) << "'"; } ss << " expected"; // Optionally append the 'where' description. if (where) ss << " " << where; SMLoc errorLoc = tok_->getStartLoc(); SourceErrorManager::SourceCoords curCoords; SourceErrorManager::SourceCoords whatCoords; // If the location of 'what' is provided, find its and the error's source // coordinates. if (whatLoc.isValid()) { sm_.findBufferLineAndLoc(errorLoc, curCoords); sm_.findBufferLineAndLoc(whatLoc, whatCoords); } if (whatCoords.isSameSourceLineAs(curCoords)) { // If the what source coordinates are on the same line as the error, show // them both. sm_.error( errorLoc, SourceErrorManager::combineIntoRange(whatLoc, errorLoc), ss.str(), Subsystem::Parser); } else { sm_.error(errorLoc, ss.str(), Subsystem::Parser); if (what && whatCoords.isValid()) sm_.note(whatLoc, what, Subsystem::Parser); } } bool JSParserImpl::need( TokenKind kind, const char *where, const char *what, SMLoc whatLoc) { if (tok_->getKind() == kind) { return true; } errorExpected(kind, where, what, whatLoc); return false; } bool JSParserImpl::eat( TokenKind kind, JSLexer::GrammarContext grammarContext, const char *where, const char *what, SMLoc whatLoc) { if (need(kind, where, what, whatLoc)) { advance(grammarContext); return true; } return false; } bool JSParserImpl::checkAndEat( TokenKind kind, JSLexer::GrammarContext grammarContext) { if (tok_->getKind() == kind) { advance(grammarContext); return true; } return false; } bool JSParserImpl::checkAndEat( UniqueString *ident, JSLexer::GrammarContext grammarContext) { if (check(ident)) { advance(grammarContext); return true; } return false; } bool JSParserImpl::checkAssign() const { return checkN( TokenKind::equal, TokenKind::starequal, TokenKind::slashequal, TokenKind::percentequal, TokenKind::plusequal, TokenKind::minusequal, TokenKind::lesslessequal, TokenKind::greatergreaterequal, TokenKind::greatergreatergreaterequal, TokenKind::starstarequal, TokenKind::pipepipeequal, TokenKind::ampampequal, TokenKind::questionquestionequal, TokenKind::ampequal, TokenKind::caretequal, TokenKind::pipeequal); } bool JSParserImpl::checkEndAssignmentExpression() const { return checkN( TokenKind::rw_in, ofIdent_, TokenKind::r_paren, TokenKind::r_brace, TokenKind::r_square, TokenKind::comma, TokenKind::semi, TokenKind::colon, TokenKind::eof) || lexer_.isNewLineBeforeCurrentToken(); } bool JSParserImpl::checkAsyncFunction() { // async [no LineTerminator here] function // ^ assert( check(asyncIdent_) && "check for async function must occur at 'async'"); // Avoid passing TokenKind::rw_function here, because parseFunctionHelper // relies on seeing `async` in order to construct its AST node. // This function must also be idempotent to allow for branching based on its // result in parseStatementListItem without having to store another flag, // for example. OptValue<TokenKind> optNext = lexer_.lookahead1(llvh::None); return optNext.hasValue() && *optNext == TokenKind::rw_function; } bool JSParserImpl::eatSemi(bool optional) { if (tok_->getKind() == TokenKind::semi) { advance(); return true; } if (tok_->getKind() == TokenKind::r_brace || tok_->getKind() == TokenKind::eof || lexer_.isNewLineBeforeCurrentToken()) { return true; } if (!optional) error(tok_->getStartLoc(), "';' expected"); return false; } void JSParserImpl::processDirective(UniqueString *directive) { seenDirectives_.push_back(directive); if (directive == useStrictIdent_) setStrictMode(true); if (directive == useStaticBuiltinIdent_) setUseStaticBuiltin(); } bool JSParserImpl::recursionDepthExceeded() { error( tok_->getStartLoc(), "Too many nested expressions/statements/declarations"); return true; } Optional<ESTree::ProgramNode *> JSParserImpl::parseProgram() { SMLoc startLoc = tok_->getStartLoc(); SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; ESTree::NodeList stmtList; if (!parseStatementList( Param{}, TokenKind::eof, true, AllowImportExport::Yes, stmtList)) return None; SMLoc endLoc = startLoc; if (!stmtList.empty()) { endLoc = stmtList.back().getEndLoc(); } auto *program = setLocation( startLoc, endLoc, new (context_) ESTree::ProgramNode(std::move(stmtList))); return program; } Optional<ESTree::FunctionDeclarationNode *> JSParserImpl::parseFunctionDeclaration(Param param, bool forceEagerly) { auto optRes = parseFunctionHelper(param, true, forceEagerly); if (!optRes) return None; return cast<ESTree::FunctionDeclarationNode>(*optRes); } Optional<ESTree::FunctionLikeNode *> JSParserImpl::parseFunctionHelper( Param param, bool isDeclaration, bool forceEagerly) { // function or async function assert(check(TokenKind::rw_function) || check(asyncIdent_)); bool isAsync = check(asyncIdent_); SMLoc startLoc = advance().Start; if (isAsync) { // async function // ^ advance(); } bool isGenerator = checkAndEat(TokenKind::star); // newParamYield setting per the grammar: // FunctionDeclaration: BindingIdentifier[?Yield, ?Await] // FunctionExpression: BindingIdentifier[~Yield, ~Await] // GeneratorFunctionDeclaration: BindingIdentifier[?Yield, ?Await] // GeneratorFunctionExpression: BindingIdentifier[+Yield, ~Await] // AsyncFunctionDeclaration: BindingIdentifier[?Yield, ?Await] // AsyncFunctionExpression: BindingIdentifier[+Yield, +Await] // AsyncGeneratorDeclaration: BindingIdentifier[?Yield, ?Await] // AsyncGeneratorExpression: BindingIdentifier[+Yield, +Await] bool nameParamYield = isDeclaration ? paramYield_ : isGenerator; llvh::SaveAndRestore<bool> saveNameParamYield(paramYield_, nameParamYield); bool nameParamAwait = isDeclaration ? paramAwait_ : isAsync; llvh::SaveAndRestore<bool> saveNameParamAwait(paramAwait_, nameParamAwait); // identifier auto optId = parseBindingIdentifier(Param{}); // If this is a default function declaration, then we can match // [+Default] function ( FormalParameters ) { FunctionBody } // so the identifier is optional and we can make it nullptr. if (isDeclaration && !param.has(ParamDefault) && !optId) { errorExpected( TokenKind::identifier, "after 'function'", "location of 'function'", startLoc); return None; } ESTree::Node *typeParams = nullptr; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::less)) { auto optTypeParams = parseTypeParamsFlow(); if (!optTypeParams) return None; typeParams = *optTypeParams; } #endif #if HERMES_PARSE_TS if (context_.getParseTS() && check(TokenKind::less)) { auto optTypeParams = parseTSTypeParameters(); if (!optTypeParams) return None; typeParams = *optTypeParams; } #endif // ( if (!need( TokenKind::l_paren, "at start of function parameter list", isDeclaration ? "function declaration starts here" : "function expression starts here", startLoc)) { return None; } ESTree::NodeList paramList; llvh::SaveAndRestore<bool> saveArgsAndBodyParamYield( paramYield_, isGenerator); llvh::SaveAndRestore<bool> saveArgsAndBodyParamAwait(paramAwait_, isAsync); if (!parseFormalParameters(param, paramList)) return None; ESTree::Node *returnType = nullptr; ESTree::Node *predicate = nullptr; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; if (!check(checksIdent_)) { auto optRet = parseTypeAnnotationFlow(annotStart); if (!optRet) return None; returnType = *optRet; } if (check(checksIdent_)) { auto optPred = parsePredicateFlow(); if (!optPred) return None; predicate = *optPred; } } #endif #if HERMES_PARSE_TS if (context_.getParseTS() && check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; if (!check(checksIdent_)) { auto optRet = parseTypeAnnotationTS(annotStart); if (!optRet) return None; returnType = *optRet; } } #endif // { if (!need( TokenKind::l_brace, isDeclaration ? "in function declaration" : "in function expression", isDeclaration ? "start of function declaration" : "start of function expression", startLoc)) { return None; } SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; // Grammar context to be used when lexing the closing brace. auto grammarContext = isDeclaration ? JSLexer::AllowRegExp : JSLexer::AllowDiv; if (pass_ == PreParse) { // Create the nodes we want to keep before the AllocationScope. ESTree::FunctionLikeNode *node; if (isDeclaration) { auto *decl = new (context_) ESTree::FunctionDeclarationNode( optId ? *optId : nullptr, std::move(paramList), nullptr, typeParams, returnType, predicate, isGenerator, isAsync); // Initialize the node with a blank body. decl->_body = new (context_) ESTree::BlockStatementNode({}); node = decl; } else { auto *expr = new (context_) ESTree::FunctionExpressionNode( optId ? *optId : nullptr, std::move(paramList), nullptr, typeParams, returnType, predicate, isGenerator, isAsync); // Initialize the node with a blank body. expr->_body = new (context_) ESTree::BlockStatementNode({}); node = expr; } AllocationScope scope(context_.getAllocator()); auto body = parseFunctionBody( Param{}, false, saveArgsAndBodyParamYield.get(), saveArgsAndBodyParamAwait.get(), grammarContext, true); if (!body) return None; return setLocation(startLoc, body.getValue(), node); } auto parsedBody = parseFunctionBody( Param{}, forceEagerly, saveArgsAndBodyParamYield.get(), saveArgsAndBodyParamAwait.get(), grammarContext, true); if (!parsedBody) return None; auto *body = parsedBody.getValue(); ESTree::FunctionLikeNode *node; if (isDeclaration) { auto *decl = new (context_) ESTree::FunctionDeclarationNode( optId ? *optId : nullptr, std::move(paramList), body, typeParams, returnType, predicate, isGenerator, isAsync); node = decl; } else { auto *expr = new (context_) ESTree::FunctionExpressionNode( optId ? *optId : nullptr, std::move(paramList), body, typeParams, returnType, predicate, isGenerator, isAsync); node = expr; } return setLocation(startLoc, body, node); } bool JSParserImpl::parseFormalParameters( Param param, ESTree::NodeList &paramList) { assert(check(TokenKind::l_paren) && "FormalParameters must start with '('"); // ( SMLoc lparenLoc = advance().Start; #if HERMES_PARSE_FLOW // The first parameter can be 'this' in Flow mode. if (context_.getParseFlow() && check(TokenKind::rw_this)) { auto *name = tok_->getResWordIdentifier(); SMLoc thisParamStart = advance().Start; SMLoc annotStart = tok_->getStartLoc(); if (!eat( TokenKind::colon, JSLexer::GrammarContext::Type, "in 'this' type annotation", "start of 'this'", thisParamStart)) return false; auto optType = parseTypeAnnotationFlow(annotStart); if (!optType) return false; ESTree::Node *type = *optType; paramList.push_back(*setLocation( thisParamStart, getPrevTokenEndLoc(), new (context_) ESTree::IdentifierNode(name, type, false))); checkAndEat(TokenKind::comma); } #endif while (!check(TokenKind::r_paren)) { if (check(TokenKind::dotdotdot)) { // BindingRestElement. auto optRestElem = parseBindingRestElement(param); if (!optRestElem) return false; paramList.push_back(*optRestElem.getValue()); break; } // BindingElement. auto optElem = parseBindingElement(param); if (!optElem) return false; paramList.push_back(*optElem.getValue()); if (!checkAndEat(TokenKind::comma)) break; } // ) if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "at end of function parameter list", "start of parameter list", lparenLoc)) { return false; } return true; } Optional<ESTree::Node *> JSParserImpl::parseStatement(Param param) { CHECK_RECURSION; #define _RET(parseFunc) \ if (auto res = (parseFunc)) \ return res.getValue(); \ else \ return None; switch (tok_->getKind()) { case TokenKind::l_brace: _RET(parseBlock(param)); case TokenKind::rw_var: _RET(parseVariableStatement(Param{})); case TokenKind::semi: _RET(parseEmptyStatement()); case TokenKind::rw_if: _RET(parseIfStatement(param.get(ParamReturn))); case TokenKind::rw_while: _RET(parseWhileStatement(param.get(ParamReturn))); case TokenKind::rw_do: _RET(parseDoWhileStatement(param.get(ParamReturn))); case TokenKind::rw_for: _RET(parseForStatement(param.get(ParamReturn))); case TokenKind::rw_continue: _RET(parseContinueStatement()); case TokenKind::rw_break: _RET(parseBreakStatement()); case TokenKind::rw_return: _RET(parseReturnStatement()); case TokenKind::rw_with: _RET(parseWithStatement(param.get(ParamReturn))); case TokenKind::rw_switch: _RET(parseSwitchStatement(param.get(ParamReturn))); case TokenKind::rw_throw: _RET(parseThrowStatement(Param{})); case TokenKind::rw_try: _RET(parseTryStatement(param.get(ParamReturn))); case TokenKind::rw_debugger: _RET(parseDebuggerStatement()); default: _RET(parseExpressionOrLabelledStatement(param.get(ParamReturn))); } #undef _RET } llvh::SmallVector<llvh::SmallString<24>, 1> JSParserImpl::copySeenDirectives() const { llvh::SmallVector<llvh::SmallString<24>, 1> copies; for (UniqueString *directive : seenDirectives_) { copies.emplace_back(directive->str()); } return copies; } Optional<ESTree::BlockStatementNode *> JSParserImpl::parseFunctionBody( Param param, bool eagerly, bool paramYield, bool paramAwait, JSLexer::GrammarContext grammarContext, bool parseDirectives) { if (pass_ == LazyParse && !eagerly) { auto startLoc = tok_->getStartLoc(); assert( preParsed_->functionInfo.count(startLoc) == 1 && "no function info stored during preparse"); PreParsedFunctionInfo functionInfo = preParsed_->functionInfo[startLoc]; SMLoc endLoc = functionInfo.end; if ((unsigned)(endLoc.getPointer() - startLoc.getPointer()) >= context_.getPreemptiveFunctionCompilationThreshold()) { lexer_.seek(endLoc); advance(grammarContext); // Emulate parsing the "use strict" directive in parseBlock. setStrictMode(functionInfo.strictMode); // PreParse collected directives idents into \c PreParsedFunctionInfo, // iterate on them and fabricate directive nodes into the body node so // the semantic validator can scan them back. ESTree::NodeList stmtList; for (const llvh::SmallString<24> &directive : functionInfo.directives) { auto *strLit = new (context_) ESTree::StringLiteralNode(lexer_.getIdentifier(directive)); auto *dirStmt = new (context_) ESTree::ExpressionStatementNode(strLit, strLit->_value); stmtList.push_back(*dirStmt); } auto *body = new (context_) ESTree::BlockStatementNode(std::move(stmtList)); body->isLazyFunctionBody = true; // Set params based on what they were at the _start_ of the function's // source, not what they are now, because they might have changed. // For example, // get [yield]() {} // means different things based on the value of paramYield at `get`, // not at the `{`. body->paramYield = paramYield; body->paramAwait = paramAwait; body->bufferId = lexer_.getBufferId(); return setLocation(startLoc, endLoc, body); } } auto body = parseBlock(ParamReturn, grammarContext, parseDirectives); if (!body) return None; if (pass_ == PreParse) { preParsed_->functionInfo[(*body)->getStartLoc()] = PreParsedFunctionInfo{ (*body)->getEndLoc(), isStrictMode(), copySeenDirectives()}; } return body; } Optional<ESTree::Node *> JSParserImpl::parseDeclaration(Param param) { CHECK_RECURSION; assert(checkDeclaration() && "invalid start for declaration"); if (check(TokenKind::rw_function) || check(asyncIdent_)) { auto fdecl = parseFunctionDeclaration(Param{}); if (!fdecl) return None; return *fdecl; } if (check(TokenKind::rw_class)) { auto optClass = parseClassDeclaration(Param{}); if (!optClass) return None; return *optClass; } if (checkN(TokenKind::rw_const, letIdent_)) { auto optLexDecl = parseLexicalDeclaration(ParamIn); if (!optLexDecl) return None; return *optLexDecl; } #if HERMES_PARSE_FLOW if (context_.getParseFlow()) { auto optDecl = parseFlowDeclaration(); if (!optDecl) return None; return *optDecl; } #endif #if HERMES_PARSE_TS if (context_.getParseTS()) { auto optDecl = parseTSDeclaration(); if (!optDecl) return None; return *optDecl; } #endif assert(false && "checkDeclaration() returned true without a declaration"); return None; } bool JSParserImpl::parseStatementListItem( Param param, AllowImportExport allowImportExport, ESTree::NodeList &stmtList) { if (checkDeclaration()) { auto decl = parseDeclaration(Param{}); if (!decl) return false; stmtList.push_back(*decl.getValue()); #if HERMES_PARSE_FLOW } else if (context_.getParseFlow() && checkDeclareType()) { // declare var, declare function, declare interface, etc. SMLoc start = advance(JSLexer::GrammarContext::Type).Start; auto decl = parseDeclareFLow(start, AllowDeclareExportType::No); if (!decl) return false; stmtList.push_back(*decl.getValue()); #endif } else if (tok_->getKind() == TokenKind::rw_import) { // 'import' can indicate an import declaration, but it's also possible a // Statement begins with a call to `import()`, so do a lookahead to see if // the next token is '('. auto optNext = lexer_.lookahead1(None); if (optNext.hasValue() && *optNext == TokenKind::l_paren) { auto stmt = parseStatement(param.get(ParamReturn)); if (!stmt) return false; stmtList.push_back(*stmt.getValue()); } else { auto importDecl = parseImportDeclaration(); if (!importDecl) { return false; } stmtList.push_back(*importDecl.getValue()); if (allowImportExport == AllowImportExport::No) { error( importDecl.getValue()->getSourceRange(), "import declaration must be at top level of module"); } } } else if (tok_->getKind() == TokenKind::rw_export) { auto exportDecl = parseExportDeclaration(); if (!exportDecl) { return false; } if (allowImportExport == AllowImportExport::Yes) { stmtList.push_back(**exportDecl); } else { error( exportDecl.getValue()->getSourceRange(), "export declaration must be at top level of module"); } } else { auto stmt = parseStatement(param.get(ParamReturn)); if (!stmt) return false; stmtList.push_back(*stmt.getValue()); } return true; } template <typename... Tail> Optional<bool> JSParserImpl::parseStatementList( Param param, TokenKind until, bool parseDirectives, AllowImportExport allowImportExport, ESTree::NodeList &stmtList, Tail... otherUntil) { if (parseDirectives) { ESTree::ExpressionStatementNode *dirStmt; while (check(TokenKind::string_literal) && (dirStmt = parseDirective()) != nullptr) { stmtList.push_back(*dirStmt); } } while (!check(TokenKind::eof) && !checkN(until, otherUntil...)) { if (!parseStatementListItem(param, allowImportExport, stmtList)) { return None; } } return true; } Optional<ESTree::BlockStatementNode *> JSParserImpl::parseBlock( Param param, JSLexer::GrammarContext grammarContext, bool parseDirectives) { // { assert(check(TokenKind::l_brace)); SMLoc startLoc = advance().Start; ESTree::NodeList stmtList; if (!parseStatementList( param, TokenKind::r_brace, parseDirectives, AllowImportExport::No, stmtList)) { return None; } // } auto *body = setLocation( startLoc, tok_, new (context_) ESTree::BlockStatementNode(std::move(stmtList))); if (!eat( TokenKind::r_brace, grammarContext, "at end of block", "block starts here", startLoc)) return None; return body; } bool JSParserImpl::validateBindingIdentifier( Param param, SMRange range, UniqueString *id, TokenKind kind) { if (id == yieldIdent_) { // yield is permitted as BindingIdentifier in the grammar, // and prohibited with static semantics. if (isStrictMode() || paramYield_) { error(range, "Unexpected usage of 'yield' as an identifier"); } } if (id == awaitIdent_) { // await is permitted as BindingIdentifier in the grammar, // and prohibited with static semantics. if (paramAwait_) { error(range, "Unexpected usage of 'await' as an identifier"); } } if (isStrictMode() && id == letIdent_) { // ES9.0 12.1.1 // BindingIdentifier : Identifier // Identifier : IdentifierName (but not ReservedWord) // It is a Syntax Error if this phrase is contained in strict mode code // and the StringValue of IdentifierName is: "implements", "interface", // "let", "package", "private", "protected", "public", "static", or // "yield". // NOTE: All except 'let' are scanned as reserved words instead of // identifiers, so we only check for `let` here. error( range, "Invalid use of strict mode reserved word as binding identifier"); } return kind == TokenKind::identifier || kind == TokenKind::rw_yield; } Optional<ESTree::IdentifierNode *> JSParserImpl::parseBindingIdentifier( Param param) { if (!check(TokenKind::identifier) && !tok_->isResWord()) { return None; } SMRange identRng = tok_->getSourceRange(); // If we have an identifier or reserved word, then store it and the kind, // and pass it to the validateBindingIdentifier function. UniqueString *id = tok_->getResWordOrIdentifier(); TokenKind kind = tok_->getKind(); if (!validateBindingIdentifier(param, tok_->getSourceRange(), id, kind)) { return None; } advance(); ESTree::Node *type = nullptr; bool optional = false; #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if (context_.getParseTypes()) { if (check(TokenKind::question)) { optional = true; advance(JSLexer::GrammarContext::Type); } if (check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotation(annotStart); if (!optType) return None; type = *optType; } } #endif return setLocation( identRng, getPrevTokenEndLoc(), new (context_) ESTree::IdentifierNode(id, type, optional)); } Optional<ESTree::VariableDeclarationNode *> JSParserImpl::parseLexicalDeclaration(Param param) { assert( (check(TokenKind::rw_var) || check(TokenKind::rw_const) || check(letIdent_)) && "parseLexicalDeclaration() expects var/const/let"); bool isConst = check(TokenKind::rw_const); auto kindIdent = tok_->getResWordOrIdentifier(); SMLoc startLoc = advance().Start; ESTree::NodeList declList; if (!parseVariableDeclarationList(param, declList, startLoc)) return None; if (!eatSemi()) return None; if (isConst) { for (const ESTree::Node &decl : declList) { const auto *varDecl = cast<ESTree::VariableDeclaratorNode>(&decl); if (!varDecl->_init) { // ES9.0 13.3.1.1 // LexicalBinding : BindingIdentifier Initializer // It is a Syntax Error if Initializer is not present and // IsConstantDeclaration of the LexicalDeclaration containing this // LexicalBinding is true. // Note that we don't perform this check in the SemanticValidator // because `const` declarations in `for` loops don't need initializers. error( varDecl->getSourceRange(), "missing initializer in const declaration"); } } } auto *res = setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::VariableDeclarationNode(kindIdent, std::move(declList))); ensureDestructuringInitialized(res); return res; } Optional<ESTree::VariableDeclarationNode *> JSParserImpl::parseVariableStatement(Param param) { return parseLexicalDeclaration(ParamIn); } Optional<ESTree::PrivateNameNode *> JSParserImpl::parsePrivateName() { assert(check(TokenKind::private_identifier)); ESTree::Node *ident = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getPrivateIdentifier(), nullptr, false)); SMLoc start = advance().Start; return setLocation( start, ident, new (context_) ESTree::PrivateNameNode(ident)); } Optional<const char *> JSParserImpl::parseVariableDeclarationList( Param param, ESTree::NodeList &declList, SMLoc declLoc) { do { auto optDecl = parseVariableDeclaration(param, declLoc); if (!optDecl) return None; declList.push_back(*optDecl.getValue()); } while (checkAndEat(TokenKind::comma)); return "OK"; } void JSParserImpl::ensureDestructuringInitialized( ESTree::VariableDeclarationNode *declNode) { for (auto &elem : declNode->_declarations) { auto *declarator = cast<ESTree::VariableDeclaratorNode>(&elem); if (!isa<ESTree::PatternNode>(declarator->_id) || declarator->_init) continue; error( declarator->_id->getSourceRange(), "destucturing declaration must be initialized"); } } Optional<ESTree::VariableDeclaratorNode *> JSParserImpl::parseVariableDeclaration(Param param, SMLoc declLoc) { ESTree::Node *target; SMLoc startLoc = tok_->getStartLoc(); if (check(TokenKind::l_square, TokenKind::l_brace)) { auto optPat = parseBindingPattern(param); if (!optPat) return None; target = *optPat; } else { auto optIdent = parseBindingIdentifier(Param{}); if (!optIdent) { errorExpected( TokenKind::identifier, "in declaration", "declaration started here", declLoc); return None; } target = *optIdent; } // No initializer? if (!check(TokenKind::equal)) { return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::VariableDeclaratorNode(nullptr, target)); }; // Parse the initializer. auto debugLoc = advance().Start; auto expr = parseAssignmentExpression( param, AllowTypedArrowFunction::Yes, CoverTypedParameters::No); if (!expr) return None; return setLocation( startLoc, getPrevTokenEndLoc(), debugLoc, new (context_) ESTree::VariableDeclaratorNode(*expr, target)); } Optional<ESTree::Node *> JSParserImpl::parseBindingPattern(Param param) { assert( check(TokenKind::l_square, TokenKind::l_brace) && "BindingPattern expects '{' or '['"); if (check(TokenKind::l_square)) { auto optAB = parseArrayBindingPattern(param); if (!optAB) return None; return *optAB; } else { auto optOB = parseObjectBindingPattern(param); if (!optOB) return None; return *optOB; } } Optional<ESTree::ArrayPatternNode *> JSParserImpl::parseArrayBindingPattern( Param param) { assert(check(TokenKind::l_square) && "ArrayBindingPattern expects '['"); // Eat the '[', recording the start location. auto startLoc = advance().Start; ESTree::NodeList elemList; if (!check(TokenKind::r_square)) { for (;;) { if (check(TokenKind::comma)) { // Elision. elemList.push_back( *setLocation(tok_, tok_, new (context_) ESTree::EmptyNode())); } else if (check(TokenKind::dotdotdot)) { // BindingRestElement. auto optRestElem = parseBindingRestElement(param); if (!optRestElem) return None; elemList.push_back(*optRestElem.getValue()); break; } else { // BindingElement. auto optElem = parseBindingElement(param); if (!optElem) return None; elemList.push_back(*optElem.getValue()); } if (!checkAndEat(TokenKind::comma)) break; if (check(TokenKind::r_square)) // Check for ",]". break; } } if (!eat( TokenKind::r_square, JSLexer::AllowDiv, "at end of array binding pattern '[...'", "location of '['", startLoc)) return None; ESTree::Node *type = nullptr; #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if (context_.getParseTypes()) { if (check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotation(annotStart); if (!optType) return None; type = *optType; } } #endif return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ArrayPatternNode(std::move(elemList), type)); } Optional<ESTree::Node *> JSParserImpl::parseBindingElement(Param param) { CHECK_RECURSION; ESTree::Node *elem; if (check(TokenKind::l_square, TokenKind::l_brace)) { auto optPat = parseBindingPattern(param); if (!optPat) return None; elem = *optPat; } else { auto optIdent = parseBindingIdentifier(param); if (!optIdent) { error( tok_->getStartLoc(), "identifier, '{' or '[' expected in binding pattern"); return None; } elem = *optIdent; } // No initializer? if (!check(TokenKind::equal)) return elem; auto optInit = parseBindingInitializer(param, elem); if (!optInit) return None; return *optInit; } Optional<ESTree::Node *> JSParserImpl::parseBindingRestElement(Param param) { assert( check(TokenKind::dotdotdot) && "BindingRestElement expected to start with '...'"); auto startLoc = advance().Start; auto optElem = parseBindingElement(param); if (!optElem) return None; if (isa<ESTree::AssignmentPatternNode>(*optElem)) { error( optElem.getValue()->getSourceRange(), "rest elemenent may not have a default initializer"); return None; } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::RestElementNode(*optElem)); } Optional<ESTree::AssignmentPatternNode *> JSParserImpl::parseBindingInitializer( Param param, ESTree::Node *left) { assert(check(TokenKind::equal) && "binding initializer requires '='"); // Parse the initializer. auto debugLoc = advance().Start; auto expr = parseAssignmentExpression(ParamIn + param); if (!expr) return None; return setLocation( left, getPrevTokenEndLoc(), debugLoc, new (context_) ESTree::AssignmentPatternNode(left, *expr)); } Optional<ESTree::ObjectPatternNode *> JSParserImpl::parseObjectBindingPattern( Param param) { assert(check(TokenKind::l_brace) && "ObjectBindingPattern expects '{'"); // Eat the '{', recording the start location. auto startLoc = advance().Start; ESTree::NodeList propList{}; if (!check(TokenKind::r_brace)) { for (;;) { if (check(TokenKind::dotdotdot)) { // BindingRestProperty. auto optRestElem = parseBindingRestProperty(param); if (!optRestElem) return None; propList.push_back(*optRestElem.getValue()); break; } auto optProp = parseBindingProperty(param); if (!optProp) return None; propList.push_back(**optProp); if (!checkAndEat(TokenKind::comma)) break; if (check(TokenKind::r_brace)) // check for ",}" break; } } if (!eat( TokenKind::r_brace, JSLexer::AllowDiv, "at end of object binding pattern '{...'", "location of '{'", startLoc)) return None; ESTree::Node *type = nullptr; #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if (context_.getParseTypes()) { if (check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotation(annotStart); if (!optType) return None; type = *optType; } } #endif return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ObjectPatternNode(std::move(propList), type)); } Optional<ESTree::PropertyNode *> JSParserImpl::parseBindingProperty( Param param) { bool computed = check(TokenKind::l_square); SMLoc startLoc = tok_->getStartLoc(); auto optKey = parsePropertyName(); if (!optKey) return None; ESTree::Node *key = optKey.getValue(); ESTree::Node *value = nullptr; bool shorthand = false; if (checkAndEat(TokenKind::colon)) { // PropertyName ":" BindingElement // ^ auto optElement = parseBindingElement(Param{}); if (!optElement) return None; value = optElement.getValue(); } else { // SingleNameBinding : // BindingIdentifier Initializer[opt] // ^ // Must validate BindingIdentifier, because there are certain identifiers // which are valid as PropertyName but not as BindingIdentifier. auto *ident = dyn_cast<ESTree::IdentifierNode>(key); if (!ident || !validateBindingIdentifier( Param{}, ident->getSourceRange(), ident->_name, TokenKind::identifier)) { error(startLoc, "identifier expected in object binding pattern"); return None; } shorthand = true; if (check(TokenKind::equal)) { // BindingIdentifier Initializer // ^ // Clone the key because parseBindingInitializer will wrap it. auto *left = setLocation( ident, ident, new (context_) ESTree::IdentifierNode(ident->_name, nullptr, false)); auto optInit = parseBindingInitializer(param + ParamIn, left); if (!optInit) return None; value = *optInit; } else { // BindingIdentifier // ^ // Shorthand property initialization, clone the key directly. value = setLocation( ident, ident, new (context_) ESTree::IdentifierNode(ident->_name, nullptr, false)); } } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::PropertyNode( key, value, initIdent_, computed, false, shorthand)); } Optional<ESTree::Node *> JSParserImpl::parseBindingRestProperty( hermes::parser::detail::Param param) { assert( check(TokenKind::dotdotdot) && "BindingRestProperty expected to start with '...'"); auto startLoc = advance().Start; // NOTE: the spec says that this cannot be another pattern, even though it // would make sense. #if 0 auto optElem = parseBindingElement(param); #else auto optElem = parseBindingIdentifier(param); #endif if (!optElem) { error( tok_->getStartLoc(), "identifier expected after '...' in object pattern"); return None; } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::RestElementNode(*optElem)); } Optional<ESTree::EmptyStatementNode *> JSParserImpl::parseEmptyStatement() { assert(check(TokenKind::semi)); auto *empty = setLocation(tok_, tok_, new (context_) ESTree::EmptyStatementNode()); advance(); return empty; } Optional<ESTree::Node *> JSParserImpl::parseExpressionOrLabelledStatement( Param param) { bool startsWithIdentifier = check(TokenKind::identifier); // ES9.0 13.5 // Lookahead cannot be any of: {, function, async function, class, let [ // Allow execution to continue because the expression may be parsed, // but report an error because it will be ambiguous whether the parse was // correct. if (checkN(TokenKind::l_brace, TokenKind::rw_function, TokenKind::rw_class) || (check(asyncIdent_) && checkAsyncFunction())) { // There's no need to stop reporting errors. error( tok_->getSourceRange(), "declaration not allowed as expression statement"); } if (check(letIdent_)) { SMLoc letLoc = advance().Start; if (check(TokenKind::l_square)) { // let [ error( {letLoc, tok_->getEndLoc()}, "ambiguous 'let [': either a 'let' binding or a member expression"); } lexer_.seek(letLoc); advance(); } SMLoc startLoc = tok_->getStartLoc(); auto optExpr = parseExpression(ParamIn, CoverTypedParameters::No); if (!optExpr) return None; // Check whether this is a label. The expression must have started with an // identifier, be just an identifier and be // followed by ':' if (startsWithIdentifier && isa<ESTree::IdentifierNode>(optExpr.getValue()) && checkAndEat(TokenKind::colon)) { auto *id = cast<ESTree::IdentifierNode>(optExpr.getValue()); ESTree::Node *body = nullptr; if (check(TokenKind::rw_function)) { auto optFunc = parseFunctionDeclaration(param); if (!optFunc) return None; /// ES9.0 13.13.1 /// It is a Syntax Error if any source text matches this rule. /// LabelledItem : FunctionDeclaration /// NOTE: GeneratorDeclarations are disallowed as part of the grammar /// as well, so all FunctionDeclarations are disallowed as labeled /// items, except via an AnnexB extension which is unsupported in /// Hermes. error( optFunc.getValue()->getSourceRange().Start, "Function declaration not allowed as body of labeled statement"); body = optFunc.getValue(); } else { // Statement auto optBody = parseStatement(param.get(ParamReturn)); if (!optBody) return None; body = optBody.getValue(); } return setLocation( id, body, new (context_) ESTree::LabeledStatementNode(id, body)); } else { if (!eatSemi()) return None; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ExpressionStatementNode(optExpr.getValue(), nullptr)); } } Optional<ESTree::IfStatementNode *> JSParserImpl::parseIfStatement( Param param) { assert(check(TokenKind::rw_if)); SMLoc startLoc = advance().Start; SMLoc condLoc = tok_->getStartLoc(); if (!eat( TokenKind::l_paren, JSLexer::AllowRegExp, "after 'if'", "location of 'if'", startLoc)) return None; auto optTest = parseExpression(); if (!optTest) return None; if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "at end of 'if' condition", "'if' condition starts here", condLoc)) return None; auto optConsequent = parseStatement(param.get(ParamReturn)); if (!optConsequent) return None; if (checkAndEat(TokenKind::rw_else)) { auto optAlternate = parseStatement(param.get(ParamReturn)); if (!optAlternate) return None; return setLocation( startLoc, optAlternate.getValue(), new (context_) ESTree::IfStatementNode( optTest.getValue(), optConsequent.getValue(), optAlternate.getValue())); } else { return setLocation( startLoc, optConsequent.getValue(), new (context_) ESTree::IfStatementNode( optTest.getValue(), optConsequent.getValue(), nullptr)); } } Optional<ESTree::WhileStatementNode *> JSParserImpl::parseWhileStatement( Param param) { assert(check(TokenKind::rw_while)); SMLoc startLoc = advance().Start; if (!eat( TokenKind::l_paren, JSLexer::AllowRegExp, "after 'while'", "location of 'while'", startLoc)) return None; auto optTest = parseExpression(); if (!optTest) return None; if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "at end of 'while' condition", "location of 'while'", startLoc)) return None; auto optBody = parseStatement(param.get(ParamReturn)); if (!optBody) return None; return setLocation( startLoc, optBody.getValue(), new (context_) ESTree::WhileStatementNode(optBody.getValue(), optTest.getValue())); } Optional<ESTree::DoWhileStatementNode *> JSParserImpl::parseDoWhileStatement( Param param) { assert(check(TokenKind::rw_do)); SMLoc startLoc = advance().Start; auto optBody = parseStatement(param.get(ParamReturn)); if (!optBody) return None; SMLoc whileLoc = tok_->getStartLoc(); if (!eat( TokenKind::rw_while, JSLexer::AllowRegExp, "at end of 'do-while'", "'do-while' starts here", startLoc)) return None; if (!eat( TokenKind::l_paren, JSLexer::AllowRegExp, "after 'do-while'", "location of 'while'", whileLoc)) return None; auto optTest = parseExpression(); if (!optTest) return None; if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "at end of 'do-while' condition", "location of 'while'", whileLoc)) return None; eatSemi(true); return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::DoWhileStatementNode(optBody.getValue(), optTest.getValue())); } Optional<ESTree::Node *> JSParserImpl::parseForStatement(Param param) { assert(check(TokenKind::rw_for)); SMLoc startLoc = advance().Start; bool await = false; SMRange awaitRng; if (paramAwait_ && check(awaitIdent_)) { awaitRng = advance(); await = true; } SMLoc lparenLoc = tok_->getStartLoc(); if (!eat( TokenKind::l_paren, JSLexer::AllowRegExp, "after 'for'", "location of 'for'", startLoc)) return None; ESTree::VariableDeclarationNode *decl = nullptr; ESTree::NodePtr expr1 = nullptr; if (checkN(TokenKind::rw_var, TokenKind::rw_const, letIdent_)) { // Productions valid here: // for ( var/let/const VariableDeclarationList // for [await] ( var/let/const VariableDeclaration SMLoc varStartLoc = tok_->getStartLoc(); auto *declIdent = tok_->getResWordOrIdentifier(); advance(); ESTree::NodeList declList; if (!parseVariableDeclarationList(Param{}, declList, varStartLoc)) return None; auto endLoc = declList.back().getEndLoc(); decl = setLocation( varStartLoc, endLoc, new (context_) ESTree::VariableDeclarationNode(declIdent, std::move(declList))); } else { // Productions valid here: // for [await] ( Expression_opt // for [await] ( LeftHandSideExpression if (!check(TokenKind::semi)) { auto optExpr1 = parseExpression(Param{}); if (!optExpr1) return None; expr1 = optExpr1.getValue(); } } if (checkN(TokenKind::rw_in, ofIdent_)) { // Productions valid here: // for [await] ( var/let/const VariableDeclaration[In] in/of // for [await] ( LeftHandSideExpression in/of if (decl && decl->_declarations.size() > 1) { error( decl->getSourceRange(), "Only one binding must be declared in a for-in/for-of loop"); return None; } // Check for destructuring pattern on the left and reparse it. if (expr1 && (isa<ESTree::ArrayExpressionNode>(expr1) || isa<ESTree::ObjectExpressionNode>(expr1))) { auto optExpr1 = reparseAssignmentPattern(expr1, false); if (!optExpr1) return None; expr1 = *optExpr1; } // Remember whether we are parsing for-in or for-of. bool const forInLoop = check(TokenKind::rw_in); advance(); if (forInLoop && await) error(awaitRng, "unexpected 'await' in for..in loop"); auto optRightExpr = forInLoop ? parseExpression() : parseAssignmentExpression(ParamIn); if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "after 'for(... in/of ...'", "location of '('", lparenLoc)) return None; auto optBody = parseStatement(param.get(ParamReturn)); if (!optBody || !optRightExpr) return None; ESTree::Node *node; if (forInLoop) { node = new (context_) ESTree::ForInStatementNode( decl ? decl : expr1, optRightExpr.getValue(), optBody.getValue()); } else { node = new (context_) ESTree::ForOfStatementNode( decl ? decl : expr1, optRightExpr.getValue(), optBody.getValue(), await); } return setLocation(startLoc, optBody.getValue(), node); } else if (checkAndEat(TokenKind::semi)) { // Productions valid here: // for ( var/let/const VariableDeclarationList[In] ; Expressionopt ; // Expressionopt ) // Statement // for ( Expression[In]opt ; Expressionopt ; Expressionopt ) Statement if (await) error(awaitRng, "unexpected 'await' in for loop without 'of'"); if (decl) ensureDestructuringInitialized(decl); ESTree::NodePtr test = nullptr; if (!check(TokenKind::semi)) { auto optTest = parseExpression(); if (!optTest) return None; test = optTest.getValue(); } if (!eat( TokenKind::semi, JSLexer::AllowRegExp, "after 'for( ... ; ...'", "location of '('", lparenLoc)) return None; ESTree::NodePtr update = nullptr; if (!check(TokenKind::r_paren)) { auto optUpdate = parseExpression(); if (!optUpdate) return None; update = optUpdate.getValue(); } if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "after 'for( ... ; ... ; ...'", "location of '('", lparenLoc)) return None; auto optBody = parseStatement(param.get(ParamReturn)); if (!optBody) return None; return setLocation( startLoc, optBody.getValue(), new (context_) ESTree::ForStatementNode( decl ? decl : expr1, test, update, optBody.getValue())); } else { errorExpected( TokenKind::semi, TokenKind::rw_in, "inside 'for'", "location of the 'for'", startLoc); return None; } } Optional<ESTree::ContinueStatementNode *> JSParserImpl::parseContinueStatement() { assert(check(TokenKind::rw_continue)); SMLoc startLoc = advance().Start; if (eatSemi(true)) return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ContinueStatementNode(nullptr)); if (!need( TokenKind::identifier, "after 'continue'", "location of 'continue'", startLoc)) return None; auto *id = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getIdentifier(), nullptr, false)); advance(); if (!eatSemi()) return None; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ContinueStatementNode(id)); } Optional<ESTree::BreakStatementNode *> JSParserImpl::parseBreakStatement() { assert(check(TokenKind::rw_break)); SMLoc startLoc = advance().Start; if (eatSemi(true)) return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::BreakStatementNode(nullptr)); if (!need( TokenKind::identifier, "after 'break'", "location of 'break'", startLoc)) return None; auto *id = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getIdentifier(), nullptr, false)); advance(); if (!eatSemi()) return None; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::BreakStatementNode(id)); } Optional<ESTree::ReturnStatementNode *> JSParserImpl::parseReturnStatement() { assert(check(TokenKind::rw_return)); SMLoc startLoc = advance().Start; if (eatSemi(true)) return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ReturnStatementNode(nullptr)); auto optArg = parseExpression(); if (!optArg) return None; if (!eatSemi()) return None; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ReturnStatementNode(optArg.getValue())); } Optional<ESTree::WithStatementNode *> JSParserImpl::parseWithStatement( Param param) { assert(check(TokenKind::rw_with)); SMLoc startLoc = advance().Start; SMLoc lparenLoc = tok_->getStartLoc(); if (!eat( TokenKind::l_paren, JSLexer::AllowRegExp, "after 'with'", "location of 'with'", startLoc)) return None; auto optExpr = parseExpression(); if (!optExpr) return None; if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "after 'with (...'", "location of '('", lparenLoc)) return None; auto optBody = parseStatement(param.get(ParamReturn)); if (!optBody) return None; return setLocation( startLoc, optBody.getValue(), new (context_) ESTree::WithStatementNode(optExpr.getValue(), optBody.getValue())); } Optional<ESTree::SwitchStatementNode *> JSParserImpl::parseSwitchStatement( Param param) { assert(check(TokenKind::rw_switch)); SMLoc startLoc = advance().Start; SMLoc lparenLoc = tok_->getStartLoc(); if (!eat( TokenKind::l_paren, JSLexer::AllowRegExp, "after 'switch'", "location of 'switch'", startLoc)) return None; auto optDiscriminant = parseExpression(); if (!optDiscriminant) return None; if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "after 'switch (...'", "location of '('", lparenLoc)) return None; SMLoc lbraceLoc = tok_->getStartLoc(); if (!eat( TokenKind::l_brace, JSLexer::AllowRegExp, "after 'switch (...)'", "'switch' starts here", startLoc)) return None; ESTree::NodeList clauseList; SMLoc defaultLocation; // location of the 'default' clause // Parse the switch body. while (!check(TokenKind::r_brace)) { SMLoc clauseStartLoc = tok_->getStartLoc(); ESTree::NodePtr testExpr = nullptr; bool ignoreClause = false; // Set to true in error recovery when we want to // parse but ignore the parsed statements. ESTree::NodeList stmtList; SMLoc caseLoc = tok_->getStartLoc(); if (checkAndEat(TokenKind::rw_case)) { auto optTestExpr = parseExpression(ParamIn, CoverTypedParameters::No); if (!optTestExpr) return None; testExpr = optTestExpr.getValue(); } else if (checkAndEat(TokenKind::rw_default)) { if (defaultLocation.isValid()) { error(clauseStartLoc, "more than one 'default' clause in 'switch'"); sm_.note(defaultLocation, "first 'default' clause was defined here"); // We want to continue parsing but ignore the statements. ignoreClause = true; } else { defaultLocation = clauseStartLoc; } } else { errorExpected( TokenKind::rw_case, TokenKind::rw_default, "inside 'switch'", "location of 'switch'", startLoc); return None; } SMLoc colonLoc = tok_->getEndLoc(); // save the location in case the clause is empty if (!eat( TokenKind::colon, JSLexer::AllowRegExp, "after 'case ...' or 'default'", "location of 'case'/'default'", caseLoc)) return None; /// case Expression : StatementList[opt] /// ^ if (!parseStatementList( param.get(ParamReturn), TokenKind::rw_default, false, AllowImportExport::No, stmtList, TokenKind::rw_case, TokenKind::r_brace)) return None; if (!ignoreClause) { auto clauseEndLoc = stmtList.empty() ? colonLoc : stmtList.back().getEndLoc(); clauseList.push_back(*setLocation( clauseStartLoc, clauseEndLoc, new (context_) ESTree::SwitchCaseNode(testExpr, std::move(stmtList)))); } } SMLoc endLoc = tok_->getEndLoc(); if (!eat( TokenKind::r_brace, JSLexer::AllowRegExp, "at end of 'switch' statement", "location of '{'", lbraceLoc)) return None; return setLocation( startLoc, endLoc, new (context_) ESTree::SwitchStatementNode( optDiscriminant.getValue(), std::move(clauseList))); } Optional<ESTree::ThrowStatementNode *> JSParserImpl::parseThrowStatement( Param param) { assert(check(TokenKind::rw_throw)); SMLoc startLoc = advance().Start; if (lexer_.isNewLineBeforeCurrentToken()) { error(tok_->getStartLoc(), "'throw' argument must be on the same line"); sm_.note(startLoc, "location of the 'throw'"); return None; } auto optExpr = parseExpression(); if (!optExpr) return None; if (!eatSemi()) return None; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ThrowStatementNode(optExpr.getValue())); } Optional<ESTree::TryStatementNode *> JSParserImpl::parseTryStatement( Param param) { assert(check(TokenKind::rw_try)); SMLoc startLoc = advance().Start; if (!need(TokenKind::l_brace, "after 'try'", "location of 'try'", startLoc)) return None; auto optTryBody = parseBlock(param.get(ParamReturn)); if (!optTryBody) return None; ESTree::CatchClauseNode *catchHandler = nullptr; ESTree::BlockStatementNode *finallyHandler = nullptr; // Parse the optional 'catch' handler. SMLoc handlerStartLoc = tok_->getStartLoc(); if (checkAndEat(TokenKind::rw_catch)) { ESTree::Node *catchParam = nullptr; if (checkAndEat(TokenKind::l_paren)) { // CatchClause param is optional. if (check(TokenKind::l_square, TokenKind::l_brace)) { auto optPattern = parseBindingPattern(Param{}); if (!optPattern) return None; catchParam = *optPattern; } else { auto optIdent = parseBindingIdentifier(Param{}); if (!optIdent) { errorExpected( TokenKind::identifier, "inside catch list", "location of 'catch'", handlerStartLoc); return None; } catchParam = *optIdent; } if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "after 'catch (...'", "location of 'catch'", handlerStartLoc)) return None; } if (!need( TokenKind::l_brace, "after 'catch(...)'", "location of 'catch'", handlerStartLoc)) return None; auto optCatchBody = parseBlock(param.get(ParamReturn)); if (!optCatchBody) return None; catchHandler = setLocation( handlerStartLoc, optCatchBody.getValue(), new (context_) ESTree::CatchClauseNode(catchParam, optCatchBody.getValue())); } // Parse the optional 'finally' handler. SMLoc finallyLoc = tok_->getStartLoc(); if (checkAndEat(TokenKind::rw_finally)) { if (!need( TokenKind::l_brace, "after 'finally'", "location of 'finally'", finallyLoc)) return None; auto optFinallyBody = parseBlock(param.get(ParamReturn)); if (!optFinallyBody) return None; finallyHandler = optFinallyBody.getValue(); } // At least one handler must be present. if (!catchHandler && !finallyHandler) { errorExpected( TokenKind::rw_catch, TokenKind::rw_finally, "after 'try' block", "location of 'try'", startLoc); return None; } // Use the last handler's location as the end location. SMLoc endLoc = finallyHandler ? finallyHandler->getEndLoc() : catchHandler->getEndLoc(); return setLocation( startLoc, endLoc, new (context_) ESTree::TryStatementNode( optTryBody.getValue(), catchHandler, finallyHandler)); } Optional<ESTree::DebuggerStatementNode *> JSParserImpl::parseDebuggerStatement() { assert(check(TokenKind::rw_debugger)); SMRange startLoc = advance(); if (!eatSemi()) return None; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::DebuggerStatementNode()); } Optional<ESTree::Node *> JSParserImpl::parsePrimaryExpression() { CHECK_RECURSION; switch (tok_->getKind()) { case TokenKind::rw_this: { auto *res = setLocation(tok_, tok_, new (context_) ESTree::ThisExpressionNode()); advance(JSLexer::AllowDiv); return res; } case TokenKind::identifier: { if (check(yieldIdent_)) { // yield is only allowed as an IdentifierReference when ParamYield is // false. if (paramYield_) { error( tok_->getSourceRange(), "Unexpected usage of 'yield' as an identifier reference"); } } if (check(asyncIdent_) && checkAsyncFunction()) { auto func = parseFunctionExpression(); if (!func) return None; return func.getValue(); } auto *res = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getIdentifier(), nullptr, false)); advance(JSLexer::AllowDiv); return res; } case TokenKind::rw_null: { auto *res = setLocation(tok_, tok_, new (context_) ESTree::NullLiteralNode()); advance(JSLexer::AllowDiv); return res; } case TokenKind::rw_true: case TokenKind::rw_false: { auto *res = setLocation( tok_, tok_, new (context_) ESTree::BooleanLiteralNode( tok_->getKind() == TokenKind::rw_true)); advance(JSLexer::AllowDiv); return res; } case TokenKind::numeric_literal: { auto *res = setLocation( tok_, tok_, new (context_) ESTree::NumericLiteralNode(tok_->getNumericLiteral())); advance(JSLexer::AllowDiv); return res; } case TokenKind::bigint_literal: { auto *res = setLocation( tok_, tok_, new (context_) ESTree::BigIntLiteralNode(tok_->getBigIntLiteral())); advance(JSLexer::AllowDiv); return res; } case TokenKind::string_literal: { auto *res = setLocation( tok_, tok_, new (context_) ESTree::StringLiteralNode(tok_->getStringLiteral())); advance(JSLexer::AllowDiv); return res; } case TokenKind::regexp_literal: { auto *res = setLocation( tok_, tok_, new (context_) ESTree::RegExpLiteralNode( tok_->getRegExpLiteral()->getBody(), tok_->getRegExpLiteral()->getFlags())); advance(JSLexer::AllowDiv); return res; } case TokenKind::l_square: { auto res = parseArrayLiteral(); if (!res) return None; return res.getValue(); } case TokenKind::l_brace: { auto res = parseObjectLiteral(); if (!res) return None; return res.getValue(); } case TokenKind::l_paren: { SMLoc startLoc = advance().Start; // Cover "()". if (check(TokenKind::r_paren)) { SMLoc endLoc = advance().End; return setLocation( startLoc, endLoc, new (context_) ESTree::CoverEmptyArgsNode()); } #if HERMES_PARSE_FLOW SMLoc startLocAfterParen = tok_->getStartLoc(); #endif ESTree::Node *expr; if (check(TokenKind::dotdotdot)) { auto optRest = parseBindingRestElement(ParamIn); if (!optRest) return None; expr = setLocation( *optRest, *optRest, new (context_) ESTree::CoverRestElementNode(*optRest)); } else { auto optExpr = parseExpression(ParamIn, CoverTypedParameters::Yes); if (!optExpr) return None; expr = *optExpr; } #if HERMES_PARSE_FLOW if (context_.getParseFlow()) { if (auto *cover = dyn_cast<ESTree::CoverTypedIdentifierNode>(expr)) { if (cover->_right && !cover->_optional) { expr = setLocation( expr, getPrevTokenEndLoc(), new (context_) ESTree::TypeCastExpressionNode( cover->_left, cover->_right)); } } else if (check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotationFlow(annotStart); if (!optType) return None; ESTree::Node *type = *optType; expr = setLocation( startLocAfterParen, getPrevTokenEndLoc(), new (context_) ESTree::TypeCastExpressionNode(expr, type)); } } #endif if (!eat( TokenKind::r_paren, JSLexer::AllowDiv, "at end of parenthesized expression", "started here", startLoc)) return None; // Record the number of parens surrounding an expression. expr->incParens(); return expr; } case TokenKind::rw_function: { auto fExpr = parseFunctionExpression(); if (!fExpr) return None; return fExpr.getValue(); } case TokenKind::rw_class: { auto optClass = parseClassExpression(); if (!optClass) return None; return optClass.getValue(); } case TokenKind::no_substitution_template: case TokenKind::template_head: { auto optTemplate = parseTemplateLiteral(Param{}); if (!optTemplate) { return None; } return optTemplate.getValue(); } #if HERMES_PARSE_JSX case TokenKind::less: if (context_.getParseJSX()) { auto optJSX = parseJSX(); if (!optJSX) return None; return optJSX.getValue(); } error( tok_->getStartLoc(), "invalid expression (possible JSX: pass -parse-jsx to parse)"); return None; #endif default: error(tok_->getStartLoc(), "invalid expression"); return None; } } Optional<ESTree::ArrayExpressionNode *> JSParserImpl::parseArrayLiteral() { assert(check(TokenKind::l_square)); SMLoc startLoc = advance().Start; ESTree::NodeList elemList; bool trailingComma = false; if (!check(TokenKind::r_square)) { for (;;) { // Elision. if (check(TokenKind::comma)) { elemList.push_back( *setLocation(tok_, tok_, new (context_) ESTree::EmptyNode())); } else if (check(TokenKind::dotdotdot)) { // Spread. auto optSpread = parseSpreadElement(); if (!optSpread) return None; elemList.push_back(**optSpread); } else { auto expr = parseAssignmentExpression(); if (!expr) return None; elemList.push_back(*expr.getValue()); } if (!checkAndEat(TokenKind::comma)) break; if (check(TokenKind::r_square)) { // Check for ",]". trailingComma = true; break; } } } SMLoc endLoc = tok_->getEndLoc(); if (!eat( TokenKind::r_square, JSLexer::AllowDiv, "at end of array literal '[...'", "location of '['", startLoc)) return None; return setLocation( startLoc, endLoc, new (context_) ESTree::ArrayExpressionNode(std::move(elemList), trailingComma)); } Optional<ESTree::ObjectExpressionNode *> JSParserImpl::parseObjectLiteral() { assert(check(TokenKind::l_brace)); SMLoc startLoc = advance().Start; ESTree::NodeList elemList; if (!check(TokenKind::r_brace)) { for (;;) { if (check(TokenKind::dotdotdot)) { // Spread. auto optSpread = parseSpreadElement(); if (!optSpread) return None; elemList.push_back(**optSpread); } else { auto prop = parsePropertyAssignment(false); if (!prop) return None; elemList.push_back(*prop.getValue()); } if (!checkAndEat(TokenKind::comma)) break; if (check(TokenKind::r_brace)) // check for ",}" break; } } SMLoc endLoc = tok_->getEndLoc(); if (!eat( TokenKind::r_brace, JSLexer::AllowDiv, "at end of object literal '{...'", "location of '{'", startLoc)) return None; return setLocation( startLoc, endLoc, new (context_) ESTree::ObjectExpressionNode(std::move(elemList))); } Optional<ESTree::Node *> JSParserImpl::parseSpreadElement() { assert(check(TokenKind::dotdotdot) && "SpreadElement must start with '...'"); auto spreadStartLoc = advance(); auto optExpr = parseAssignmentExpression(); if (!optExpr) return None; return setLocation( spreadStartLoc, getPrevTokenEndLoc(), new (context_) ESTree::SpreadElementNode(*optExpr)); } Optional<ESTree::Node *> JSParserImpl::parsePropertyAssignment(bool eagerly) { SMLoc startLoc = tok_->getStartLoc(); ESTree::NodePtr key = nullptr; SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; bool computed = false; bool generator = false; bool async = false; bool method = false; if (check(getIdent_)) { UniqueString *ident = tok_->getResWordOrIdentifier(); SMRange identRng = tok_->getSourceRange(); advance(); // This could either be a getter, or a property named 'get'. if (check(TokenKind::colon, TokenKind::l_paren)) { // This is just a property. key = setLocation( identRng, identRng, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); #if HERMES_PARSE_FLOW || HERMES_PARSE_TS } else if (context_.getParseTypes() && check(TokenKind::less)) { // This is a method definition. method = true; key = setLocation( identRng, identRng, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); #endif } else if (check(TokenKind::comma, TokenKind::r_brace)) { // If the next token is "," or "}", this is a shorthand property // definition. key = setLocation( identRng, identRng, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); auto *value = setLocation( key, key, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); return setLocation( startLoc, value, new (context_) ESTree::PropertyNode(key, value, initIdent_, false, false, true)); } else { // A getter method. computed = check(TokenKind::l_square); auto optKey = parsePropertyName(); if (!optKey) return None; if (!eat( TokenKind::l_paren, JSLexer::AllowRegExp, "in getter declaration", "start of getter declaration", startLoc)) return None; if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "in empty getter parameter list", "start of getter declaration", startLoc)) return None; ESTree::Node *returnType = nullptr; #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if (context_.getParseTypes() && check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optRet = parseTypeAnnotation(annotStart); if (!optRet) return None; returnType = *optRet; } #endif llvh::SaveAndRestore<bool> oldParamYield(paramYield_, false); llvh::SaveAndRestore<bool> oldParamAwait(paramAwait_, false); if (!need( TokenKind::l_brace, "in getter declaration", "start of getter declaration", startLoc)) return None; auto block = parseFunctionBody( ParamReturn, eagerly, oldParamYield.get(), oldParamAwait.get(), JSLexer::AllowRegExp, true); if (!block) return None; auto *funcExpr = new (context_) ESTree::FunctionExpressionNode( nullptr, ESTree::NodeList{}, block.getValue(), nullptr, returnType, /* predicate */ nullptr, false, false); funcExpr->isMethodDefinition = true; setLocation(startLoc, block.getValue(), funcExpr); auto *node = new (context_) ESTree::PropertyNode( optKey.getValue(), funcExpr, getIdent_, computed, false, false); return setLocation(startLoc, block.getValue(), node); } } else if (check(setIdent_)) { UniqueString *ident = tok_->getResWordOrIdentifier(); SMRange identRng = tok_->getSourceRange(); advance(); // This could either be a setter, or a property named 'set'. if (check(TokenKind::colon, TokenKind::l_paren)) { // This is just a property. key = setLocation( identRng, identRng, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); #if HERMES_PARSE_FLOW || HERMES_PARSE_TS } else if (context_.getParseTypes() && check(TokenKind::less)) { // This is a method definition. method = true; key = setLocation( identRng, identRng, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); #endif } else if (check(TokenKind::comma, TokenKind::r_brace)) { // If the next token is "," or "}", this is a shorthand property // definition. key = setLocation( identRng, identRng, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); auto *value = setLocation( key, key, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); return setLocation( startLoc, value, new (context_) ESTree::PropertyNode(key, value, initIdent_, false, false, true)); } else { // A setter method. computed = check(TokenKind::l_square); auto optKey = parsePropertyName(); if (!optKey) return None; llvh::SaveAndRestore<bool> oldParamYield(paramYield_, false); llvh::SaveAndRestore<bool> oldParamAwait(paramAwait_, false); ESTree::NodeList params; eat(TokenKind::l_paren, JSLexer::AllowRegExp, "in setter declaration", "start of setter declaration", startLoc); // PropertySetParameterList -> FormalParameter -> BindingElement auto optParam = parseBindingElement(Param{}); if (!optParam) return None; params.push_back(**optParam); if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "at end of setter parameter list", "start of setter declaration", startLoc)) return None; ESTree::Node *returnType = nullptr; #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if (context_.getParseTypes() && check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optRet = parseTypeAnnotation(annotStart); if (!optRet) return None; returnType = *optRet; } #endif if (!need( TokenKind::l_brace, "in setter declaration", "start of setter declaration", startLoc)) return None; auto block = parseFunctionBody( ParamReturn, eagerly, oldParamYield.get(), oldParamAwait.get(), JSLexer::AllowRegExp, true); if (!block) return None; auto *funcExpr = new (context_) ESTree::FunctionExpressionNode( nullptr, std::move(params), block.getValue(), nullptr, returnType, /* predicate */ nullptr, false, false); funcExpr->isMethodDefinition = true; setLocation(startLoc, block.getValue(), funcExpr); auto *node = new (context_) ESTree::PropertyNode( optKey.getValue(), funcExpr, setIdent_, computed, false, false); return setLocation(startLoc, block.getValue(), node); } } else if (check(asyncIdent_)) { UniqueString *ident = tok_->getResWordOrIdentifier(); SMRange identRng = tok_->getSourceRange(); advance(); // This could either be an async function, or a property named 'async'. if (check(TokenKind::colon, TokenKind::l_paren)) { // This is just a property (or method) called 'async'. key = setLocation( identRng, identRng, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); #if HERMES_PARSE_FLOW || HERMES_PARSE_TS } else if (context_.getParseTypes() && check(TokenKind::less)) { // This is a method definition. method = true; key = setLocation( identRng, identRng, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); #endif } else if (check(TokenKind::comma, TokenKind::r_brace)) { // If the next token is "," or "}", this is a shorthand property // definition. key = setLocation( identRng, identRng, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); auto *value = setLocation( key, key, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); return setLocation( startLoc, value, new (context_) ESTree::PropertyNode(key, value, initIdent_, false, false, true)); } else { // This is an async function, parse the key and set `async` to true. async = true; method = true; generator = checkAndEat(TokenKind::star); computed = check(TokenKind::l_square); auto optKey = parsePropertyName(); if (!optKey) return None; key = optKey.getValue(); } } else if (check(TokenKind::identifier)) { auto *ident = tok_->getIdentifier(); key = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); advance(); // If the next token is "," or "}", this is a shorthand property definition. if (check(TokenKind::comma, TokenKind::r_brace)) { auto *value = setLocation( key, key, new (context_) ESTree::IdentifierNode(ident, nullptr, false)); return setLocation( startLoc, value, new (context_) ESTree::PropertyNode(key, value, initIdent_, false, false, true)); } } else { generator = checkAndEat(TokenKind::star); computed = check(TokenKind::l_square); auto optKey = parsePropertyName(); if (!optKey) return None; key = optKey.getValue(); } ESTree::Node *value; bool shorthand = false; if (isa<ESTree::IdentifierNode>(key) && check(TokenKind::equal)) { // Check for CoverInitializedName: IdentifierReference Initializer auto startLoc = advance().Start; auto optInit = parseAssignmentExpression(); if (!optInit) return None; shorthand = true; value = setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::CoverInitializerNode(*optInit)); } else if (check(TokenKind::l_paren, TokenKind::less) || async) { // Try this branch when we have '(' or '<' to indicate a method // or when we know this is async, because async must also indicate a method, // and we must avoid parsing ordinary properties from ':'. // Parse the MethodDefinition manually here. // Do not use `parseClassElement` because we had to parsePropertyName // in this function ourselves and check for SingleNameBindings, which are // not parsed with `parsePropertyName`. // MethodDefinition: // PropertyName "(" UniqueFormalParameters ")" "{" FunctionBody "}" // ^ llvh::SaveAndRestore<bool> oldParamYield(paramYield_, generator); llvh::SaveAndRestore<bool> oldParamAwait(paramAwait_, async); method = true; ESTree::Node *typeParams = nullptr; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::less)) { auto optTypeParams = parseTypeParamsFlow(); if (!optTypeParams) return None; typeParams = *optTypeParams; } #endif #if HERMES_PARSE_TS if (context_.getParseTS() && check(TokenKind::less)) { auto optTypeParams = parseTSTypeParameters(); if (!optTypeParams) return None; typeParams = *optTypeParams; } #endif // ( if (!need( TokenKind::l_paren, "in method definition", "start of method definition", startLoc)) return None; ESTree::NodeList args{}; if (!parseFormalParameters(Param{}, args)) return None; ESTree::Node *returnType = nullptr; #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if (context_.getParseTypes() && check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optRet = parseTypeAnnotation(annotStart); if (!optRet) return None; returnType = *optRet; } #endif if (!need( TokenKind::l_brace, "in method definition", "start of method definition", startLoc)) return None; auto optBody = parseFunctionBody( ParamReturn, eagerly, oldParamYield.get(), oldParamAwait.get(), JSLexer::AllowRegExp, true); if (!optBody) return None; auto *funcExpr = new (context_) ESTree::FunctionExpressionNode( nullptr, std::move(args), optBody.getValue(), typeParams, returnType, /* predicate */ nullptr, generator, async); funcExpr->isMethodDefinition = true; setLocation(startLoc, optBody.getValue(), funcExpr); value = funcExpr; } else { if (!eat( TokenKind::colon, JSLexer::AllowRegExp, "in property initialization", "start of property initialization", startLoc)) return None; auto optValue = parseAssignmentExpression(); if (!optValue) return None; value = *optValue; } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::PropertyNode( key, value, initIdent_, computed, method, shorthand)); } Optional<ESTree::Node *> JSParserImpl::parsePropertyName() { switch (tok_->getKind()) { case TokenKind::string_literal: { auto *res = setLocation( tok_, tok_, new (context_) ESTree::StringLiteralNode(tok_->getStringLiteral())); advance(); return res; } case TokenKind::numeric_literal: { auto *res = setLocation( tok_, tok_, new (context_) ESTree::NumericLiteralNode(tok_->getNumericLiteral())); advance(); return res; } case TokenKind::identifier: { auto *res = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getIdentifier(), nullptr, false)); advance(); return res; } case TokenKind::l_square: { SMLoc start = advance().Start; auto optExpr = parseAssignmentExpression(ParamIn); if (!optExpr) { return None; } if (!need( TokenKind::r_square, "at end of computed property key", "start of property key", start)) { return llvh::None; } advance(); return *optExpr; } default: if (tok_->isResWord()) { auto *res = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordIdentifier(), nullptr, false)); advance(); return res; } else { error( tok_->getSourceRange(), "invalid property name - must be a string, number or identifier"); return None; } } } Optional<ESTree::Node *> JSParserImpl::parseTemplateLiteral(Param param) { assert(checkTemplateLiteral() && "invalid template literal start"); SMLoc start = tok_->getStartLoc(); ESTree::NodeList quasis; ESTree::NodeList expressions; /// Push the current TemplateElement onto quasis and advance the lexer. /// \param tail true if pushing the last element. /// \return false on failure. auto pushTemplateElement = [&quasis, &param, this](bool tail) -> bool { if (tok_->getTemplateLiteralContainsNotEscapes() && !param.has(ParamTagged)) { error( tok_->getSourceRange(), "untagged template literal contains invalid escape sequence"); return false; } auto *quasi = setLocation( tok_, tok_, new (context_) ESTree::TemplateElementNode( tail, tok_->getTemplateValue(), tok_->getTemplateRawValue())); quasis.push_back(*quasi); return true; }; // TemplateSpans while ( !check(TokenKind::no_substitution_template, TokenKind::template_tail)) { // TemplateSpans // Alternate TemplateMiddle and Expression until TemplateTail. if (!check(TokenKind::template_head, TokenKind::template_middle)) { error(tok_->getSourceRange(), "expected template literal"); return None; } // First, push the TemplateElement. if (!pushTemplateElement(false)) return None; SMLoc subStart = advance().Start; // Parse the next expression and add it to the expressions. auto optExpr = parseExpression(ParamIn); if (!optExpr) return None; expressions.push_back(*optExpr.getValue()); if (!check(TokenKind::r_brace)) { errorExpected( TokenKind::r_brace, "at end of substition in template literal", "start of substitution", subStart); return None; } // The } at the end of the expression must be rescanned as a // TemplateMiddle or TemplateTail. lexer_.rescanRBraceInTemplateLiteral(); } // TemplateTail or NoSubstitutionTemplate if (!pushTemplateElement(true)) return None; return setLocation( start, advance().End, new (context_) ESTree::TemplateLiteralNode( std::move(quasis), std::move(expressions))); } Optional<ESTree::FunctionExpressionNode *> JSParserImpl::parseFunctionExpression(bool forceEagerly) { auto optRes = parseFunctionHelper(Param{}, false, forceEagerly); if (!optRes) return None; return cast<ESTree::FunctionExpressionNode>(*optRes); } Optional<ESTree::Node *> JSParserImpl::parseOptionalExpressionExceptNew( IsConstructorCall isConstructorCall) { SMLoc startLoc = tok_->getStartLoc(); ESTree::NodePtr expr; if (check(TokenKind::rw_super)) { // SuperProperty can be used the same way as PrimaryExpression, but // must not have a TemplateLiteral immediately after the `super` keyword. expr = setLocation(tok_, tok_, new (context_) ESTree::SuperNode()); advance(); if (!checkN(TokenKind::l_paren, TokenKind::l_square, TokenKind::period)) { errorExpected( {TokenKind::l_paren, TokenKind::l_square, TokenKind::period}, "after 'super' keyword", "location of 'super'", startLoc); return None; } } else if (check(TokenKind::rw_import)) { // ImportCall must be a call with an AssignmentExpression as the // argument. advance(); if (!eat( TokenKind::l_paren, JSLexer::AllowRegExp, "in import call", "location of 'import'", startLoc)) return None; auto optSource = parseAssignmentExpression(ParamIn); if (!optSource) return None; ESTree::Node *source = *optSource; checkAndEat(TokenKind::comma); ESTree::Node *attributes = nullptr; if (!check(TokenKind::r_paren)) { auto optAttributes = parseAssignmentExpression(); if (!optAttributes) return None; attributes = *optAttributes; checkAndEat(TokenKind::comma); } SMLoc endLoc = tok_->getEndLoc(); if (!eat( TokenKind::r_paren, JSLexer::AllowRegExp, "in import call", "location of 'import'", startLoc)) return None; expr = setLocation( startLoc, endLoc, new (context_) ESTree::ImportExpressionNode(source, attributes)); } else { auto primExpr = parsePrimaryExpression(); if (!primExpr) return None; expr = primExpr.getValue(); } return parseOptionalExpressionExceptNew_tail( isConstructorCall, startLoc, expr); } Optional<ESTree::Node *> JSParserImpl::parseOptionalExpressionExceptNew_tail( IsConstructorCall isConstructorCall, SMLoc startLoc, ESTree::Node *expr) { SMLoc objectLoc = startLoc; bool seenOptionalChain = false; llvh::SaveAndRestore<unsigned> savedRecursionDepth{ recursionDepth_, recursionDepth_}; while ( checkN(TokenKind::l_square, TokenKind::period, TokenKind::questiondot) || checkTemplateLiteral()) { ++recursionDepth_; if (LLVM_UNLIKELY(recursionDepthCheck())) { return None; } SMLoc nextObjectLoc = tok_->getStartLoc(); if (checkN( TokenKind::l_square, TokenKind::period, TokenKind::questiondot)) { if (check(TokenKind::questiondot)) { seenOptionalChain = true; if (isConstructorCall == IsConstructorCall::Yes) { // Report the error here, but continue on because we can still parse // the rest of the file. error( tok_->getSourceRange(), "Constructor calls may not contain an optional chain"); } } // MemberExpression [ Expression ] // MemberExpression . IdentifierName // MemberExpression OptionalChain auto msel = parseMemberSelect(startLoc, objectLoc, expr, seenOptionalChain); if (!msel) return None; objectLoc = nextObjectLoc; expr = msel.getValue(); } else { assert(checkTemplateLiteral()); if (isa<ESTree::SuperNode>(expr)) { error( expr->getSourceRange(), "invalid use of 'super' as a template literal tag"); return None; } if (seenOptionalChain) { // This construction is allowed by the grammar but accounted for by // static semantics in order to prevent ASI from being used like this: // \code // a?.b // `abc`; // \endcode error( tok_->getSourceRange(), "invalid use of tagged template literal in optional chain"); sm_.note(expr->getSourceRange(), "location of optional chain"); } // MemberExpression TemplateLiteral auto optTemplate = parseTemplateLiteral(ParamTagged); if (!optTemplate) return None; expr = setLocation( startLoc, optTemplate.getValue(), new (context_) ESTree::TaggedTemplateExpressionNode( expr, optTemplate.getValue())); objectLoc = nextObjectLoc; } } return expr; } Optional<const char *> JSParserImpl::parseArguments( ESTree::NodeList &argList, SMLoc &endLoc) { assert(check(TokenKind::l_paren)); SMLoc startLoc = advance().Start; if (!check(TokenKind::r_paren)) { for (;;) { SMLoc argStart = tok_->getStartLoc(); bool isSpread = checkAndEat(TokenKind::dotdotdot); auto arg = parseAssignmentExpression(); if (!arg) return None; if (isSpread) { argList.push_back(*setLocation( argStart, getPrevTokenEndLoc(), new (context_) ESTree::SpreadElementNode(arg.getValue()))); } else { argList.push_back(*arg.getValue()); } if (!checkAndEat(TokenKind::comma)) break; // Check for ",)". if (check(TokenKind::r_paren)) break; } } endLoc = tok_->getEndLoc(); if (!eat( TokenKind::r_paren, JSLexer::AllowDiv, "at end of function call", "location of '('", startLoc)) return None; return "OK"; } Optional<ESTree::Node *> JSParserImpl::parseMemberSelect( SMLoc startLoc, SMLoc objectLoc, ESTree::NodePtr expr, bool seenOptionalChain) { assert( checkN(TokenKind::l_square, TokenKind::period, TokenKind::questiondot)); SMLoc puncLoc = tok_->getStartLoc(); bool optional = checkAndEat(TokenKind::questiondot); if (checkAndEat(TokenKind::l_square)) { // Parsing another Expression directly without going through // PrimaryExpression. This can overflow, so check. CHECK_RECURSION; auto propExpr = parseExpression(); if (!propExpr) return None; SMLoc endLoc = tok_->getEndLoc(); if (!eat( TokenKind::r_square, JSLexer::AllowDiv, "at end of member expression '[...'", "location iof '['", puncLoc)) return None; if (optional || seenOptionalChain) { return setLocation( startLoc, endLoc, puncLoc, new (context_) ESTree::OptionalMemberExpressionNode( expr, propExpr.getValue(), true, optional)); } return setLocation( startLoc, endLoc, puncLoc, new (context_) ESTree::MemberExpressionNode(expr, propExpr.getValue(), true)); } else if ( checkAndEat(TokenKind::period) || (optional && !(check(TokenKind::l_paren) || (context_.getParseFlow() && check(TokenKind::less))))) { if (!check(TokenKind::identifier, TokenKind::private_identifier) && !tok_->isResWord()) { // Just use the pattern here, even though we know it will fail. if (!need( TokenKind::identifier, "after '.' or '?.' in member expression", "start of member expression", objectLoc)) return None; } ESTree::Node *id = nullptr; if (check(TokenKind::private_identifier)) { auto optId = parsePrivateName(); if (!optId) return None; id = *optId; } else { id = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); advance(JSLexer::AllowDiv); } if (optional || seenOptionalChain) { return setLocation( startLoc, id, puncLoc, new (context_) ESTree::OptionalMemberExpressionNode(expr, id, false, optional)); } return setLocation( startLoc, id, puncLoc, new (context_) ESTree::MemberExpressionNode(expr, id, false)); } else { assert( optional && (check(TokenKind::l_paren) || (context_.getParseFlow() && check(TokenKind::less))) && "must be ?.() at this point"); // ?. Arguments : // ?. ( ArgumentList ) // ^ auto debugLoc = tok_->getStartLoc(); ESTree::NodePtr typeArgs = nullptr; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::less)) { auto optTypeArgs = parseTypeArgsFlow(); if (!optTypeArgs) { return None; } typeArgs = *optTypeArgs; if (!need( TokenKind::l_paren, "after type arguments in optional call", "start of optional call", objectLoc)) return None; } #endif #if HERMES_PARSE_TS if (context_.getParseTS() && check(TokenKind::less)) { auto optTypeArgs = parseTSTypeArguments(); if (!optTypeArgs) { return None; } typeArgs = *optTypeArgs; if (!need( TokenKind::l_paren, "after type arguments in optional call", "start of optional call", objectLoc)) return None; } #endif ESTree::NodeList argList; SMLoc endLoc; if (!parseArguments(argList, endLoc)) return None; return setLocation( startLoc, endLoc, debugLoc, new (context_) ESTree::OptionalCallExpressionNode( expr, typeArgs, std::move(argList), true)); } llvm_unreachable("Invalid token in parseMemberSelect"); } Optional<ESTree::Node *> JSParserImpl::parseCallExpression( SMLoc startLoc, ESTree::NodePtr expr, ESTree::NodePtr typeArgs, bool seenOptionalChain, bool optional) { assert(checkN( TokenKind::l_paren, TokenKind::no_substitution_template, TokenKind::template_head)); SMLoc objectLoc = startLoc; for (;;) { #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if ((context_.getParseFlowAmbiguous() || context_.getParseTS()) && !typeArgs && check(TokenKind::less)) { JSLexer::SavePoint savePoint{&lexer_}; // Each call in a chain may have type arguments. // As such, we must attempt to parse them upon encountering '<', // but roll back if it just ended up being a comparison operator. SourceErrorManager::SaveAndSuppressMessages suppress{ &sm_, Subsystem::Parser}; auto optTypeArgs = context_.getParseTS() ? parseTSTypeArguments() : parseTypeArgsFlow(); if (optTypeArgs && check(TokenKind::l_paren)) { // Call expression with type arguments. typeArgs = *optTypeArgs; } else { // Failed to parse a call expression with type arguments, // simply roll back and start again. savePoint.restore(); } } #endif if (check(TokenKind::l_paren)) { auto debugLoc = tok_->getStartLoc(); ESTree::NodeList argList; SMLoc endLoc; // parseArguments can result in another call to parseCallExpression // without parsing another primary or declaration. CHECK_RECURSION; if (!parseArguments(argList, endLoc)) return None; if (seenOptionalChain) { expr = setLocation( startLoc, endLoc, debugLoc, new (context_) ESTree::OptionalCallExpressionNode( expr, typeArgs, std::move(argList), optional)); } else { expr = setLocation( startLoc, endLoc, debugLoc, new (context_) ESTree::CallExpressionNode(expr, typeArgs, std::move(argList))); } // typeArgs have been used, discard them so the next item in the call // chain can populate them if necessary. typeArgs = nullptr; } else if (checkN( TokenKind::l_square, TokenKind::period, TokenKind::questiondot)) { if (check(TokenKind::questiondot)) { seenOptionalChain = true; } SMLoc nextObjectLoc = tok_->getStartLoc(); auto msel = parseMemberSelect(startLoc, objectLoc, expr, seenOptionalChain); if (!msel) return None; objectLoc = nextObjectLoc; expr = msel.getValue(); } else if (check( TokenKind::no_substitution_template, TokenKind::template_head)) { auto debugLoc = tok_->getStartLoc(); auto optTemplate = parseTemplateLiteral(ParamTagged); if (!optTemplate) return None; expr = setLocation( startLoc, optTemplate.getValue(), debugLoc, new (context_) ESTree::TaggedTemplateExpressionNode( expr, optTemplate.getValue())); } else { break; } } return expr; } // Parsing NewExpression, MemberExpression and CallExpression is tricky. // // The key realizations are that NewExpression can be one or more // constructor calls without arguments (the s-expressions below represent // productions that have been recursively matched, not AST nodes): // // new foo -> // (NewExpression (MemberExpression)) // new new foo -> // (NewExpression (NewExpression (MemberExpression))) // // MemberExpression can be one or more constructor calls with arguments: // // new foo() -> // (MemberExpression (MemberExpression)) // new new foo()() // (MemberExpression (MemberExpression (MemberExpression)) // // Call expression are formed from arguments that don't match up with a `new`: // // foo() -> // (CallExpression (MemberExpression)) // new foo()() // (CallExpression (MemberExpression (MemberExpression))) Optional<ESTree::Node *> JSParserImpl::parseNewExpressionOrOptionalExpression( IsConstructorCall isConstructorCall) { if (!check(TokenKind::rw_new)) return parseOptionalExpressionExceptNew(isConstructorCall); SMRange newRange = advance(); if (checkAndEat(TokenKind::period)) { // NewTarget: new . target // ^ if (!check(targetIdent_)) { error(tok_->getSourceRange(), "'target' expected in member expression"); sm_.note(newRange.Start, "start of member expression"); return None; } auto *meta = setLocation( newRange, newRange, new (context_) ESTree::IdentifierNode(newIdent_, nullptr, false)); auto *prop = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(targetIdent_, nullptr, false)); advance(); auto *expr = setLocation( meta, prop, new (context_) ESTree::MetaPropertyNode(meta, prop)); return parseOptionalExpressionExceptNew_tail( isConstructorCall, newRange.Start, expr); } CHECK_RECURSION; auto optExpr = parseNewExpressionOrOptionalExpression(IsConstructorCall::Yes); if (!optExpr) return None; ESTree::NodePtr expr = optExpr.getValue(); ESTree::Node *typeArgs = nullptr; #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if ((context_.getParseFlowAmbiguous() || context_.getParseTS()) && check(TokenKind::less)) { JSLexer::SavePoint savePoint{&lexer_}; // Attempt to parse type args upon encountering '<', // but roll back if it just ended up being a comparison operator. SourceErrorManager::SaveAndSuppressMessages suppress{ &sm_, Subsystem::Parser}; auto optTypeArgs = context_.getParseTS() ? parseTSTypeArguments() : parseTypeArgsFlow(); if (optTypeArgs) { // New expression with type arguments. typeArgs = *optTypeArgs; } else { // Failed to parse a call expression with type arguments, // simply roll back and start again. savePoint.restore(); } } #endif // Do we have arguments to a child MemberExpression? If yes, then it really // was a 'new MemberExpression(args)', otherwise it is a NewExpression if (!check(TokenKind::l_paren)) { return setLocation( newRange, getPrevTokenEndLoc(), new (context_) ESTree::NewExpressionNode(expr, typeArgs, ESTree::NodeList{})); } auto debugLoc = tok_->getStartLoc(); ESTree::NodeList argList; SMLoc endLoc; if (!parseArguments(argList, endLoc)) return None; expr = setLocation( newRange, endLoc, debugLoc, new (context_) ESTree::NewExpressionNode(expr, typeArgs, std::move(argList))); SMLoc objectLoc = newRange.Start; while ( checkN(TokenKind::l_square, TokenKind::period, TokenKind::questiondot)) { SMLoc nextObjectLoc = tok_->getStartLoc(); auto optMSel = parseMemberSelect(newRange.Start, objectLoc, expr, false); if (!optMSel) return None; objectLoc = nextObjectLoc; expr = optMSel.getValue(); } return expr; } Optional<ESTree::Node *> JSParserImpl::parseLeftHandSideExpression() { SMLoc startLoc = tok_->getStartLoc(); auto optExpr = parseNewExpressionOrOptionalExpression(IsConstructorCall::No); if (!optExpr) return None; auto *expr = optExpr.getValue(); bool optional = checkAndEat(TokenKind::questiondot); bool seenOptionalChain = optional || (expr->getParens() == 0 && (llvh::isa<ESTree::OptionalMemberExpressionNode>(expr) || llvh::isa<ESTree::OptionalCallExpressionNode>(expr))); ESTree::Node *typeArgs = nullptr; #if HERMES_PARSE_FLOW || HERMES_PARSE_TS // If the less than sign is immediately following a question dot then it // cannot be a binary expression and is unambiguously Flow type syntax. if (((optional ? context_.getParseFlow() : context_.getParseFlowAmbiguous()) || context_.getParseTS()) && check(TokenKind::less)) { JSLexer::SavePoint savePoint{&lexer_}; // Suppress messages from the parser while still displaying lexer messages. SourceErrorManager::SaveAndSuppressMessages suppress{ &sm_, Subsystem::Parser}; auto optTypeArgs = context_.getParseTS() ? parseTSTypeArguments() : parseTypeArgsFlow(); if (optTypeArgs && check(TokenKind::l_paren)) { // Call expression with type arguments. typeArgs = *optTypeArgs; } else { // Failed to parse a call expression with type arguments, // simply roll back and start again. savePoint.restore(); } } #endif // Is this a CallExpression? if (checkN( TokenKind::l_paren, TokenKind::no_substitution_template, TokenKind::template_head)) { auto optCallExpr = parseCallExpression( startLoc, expr, typeArgs, seenOptionalChain, optional); if (!optCallExpr) return None; expr = optCallExpr.getValue(); } return expr; } Optional<ESTree::Node *> JSParserImpl::parsePostfixExpression() { SMLoc startLoc = tok_->getStartLoc(); auto optLHandExpr = parseLeftHandSideExpression(); if (!optLHandExpr) return None; if (check(TokenKind::plusplus, TokenKind::minusminus) && !lexer_.isNewLineBeforeCurrentToken()) { auto *res = setLocation( startLoc, tok_, tok_, new (context_) ESTree::UpdateExpressionNode( getTokenIdent(tok_->getKind()), optLHandExpr.getValue(), false)); advance(JSLexer::AllowDiv); return res; } else { return optLHandExpr.getValue(); } } Optional<ESTree::Node *> JSParserImpl::parseUnaryExpression() { SMLoc startLoc = tok_->getStartLoc(); switch (tok_->getKind()) { case TokenKind::rw_delete: case TokenKind::rw_void: case TokenKind::rw_typeof: case TokenKind::plus: case TokenKind::minus: case TokenKind::tilde: case TokenKind::exclaim: { UniqueString *op = getTokenIdent(tok_->getKind()); advance(); CHECK_RECURSION; auto expr = parseUnaryExpression(); if (!expr) return None; if (check(TokenKind::starstar)) { // ExponentiationExpression only allows UpdateExpressionNode on the // left. The simplest way to enforce that the left operand is not // an unparenthesized UnaryExpression is to check here. error( {startLoc, tok_->getEndLoc()}, "Unary operator before ** must use parens to disambiguate"); } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::UnaryExpressionNode(op, expr.getValue(), true)); } case TokenKind::plusplus: case TokenKind::minusminus: { UniqueString *op = getTokenIdent(tok_->getKind()); advance(); CHECK_RECURSION; auto expr = parseUnaryExpression(); if (!expr) return None; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::UpdateExpressionNode(op, expr.getValue(), true)); } #if HERMES_PARSE_TS case TokenKind::less: if (context_.getParseTS() && !context_.getParseJSX()) { // TSTypeAssertions are only parsed if JSX is disabled, // so there's no backtracking necessary here. // < Type > UnaryExpression // ^ advance(JSLexer::GrammarContext::Type); auto optType = parseTypeAnnotationTS(); if (!optType) return None; if (!eat( TokenKind::greater, JSLexer::GrammarContext::AllowRegExp, "in type assertion", "start of assertion", startLoc)) return None; CHECK_RECURSION; auto optExpr = parseUnaryExpression(); if (!optExpr) return None; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::TSTypeAssertionNode(*optType, *optExpr)); } #endif case TokenKind::identifier: if (check(awaitIdent_) && paramAwait_) { advance(); CHECK_RECURSION; auto optExpr = parseUnaryExpression(); if (!optExpr) return None; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::AwaitExpressionNode(optExpr.getValue())); } // Fall-through to default for all other identifiers. LLVM_FALLTHROUGH; default: return parsePostfixExpression(); } } namespace { /// Associates precedence levels with binary operators. Higher precedences are /// represented by higher values. /// \returns the precedence level starting from 1, or 0 if not a binop. inline unsigned getPrecedence(TokenKind kind) { // Record the precedence of all binary operators. static const unsigned precedence[] = { #define TOK(...) 0, #define BINOP(name, str, precedence) precedence, // There are two reserved words that are binary operators. #define RESWORD(name) \ (TokenKind::rw_##name == TokenKind::rw_in || \ TokenKind::rw_##name == TokenKind::rw_instanceof \ ? 8 \ : 0), #include "hermes/Parser/TokenKinds.def" }; return precedence[static_cast<unsigned>(kind)]; } /// \return true if \p kind is left associative, false if right associative. inline bool isLeftAssoc(TokenKind kind) { return kind != TokenKind::starstar; } /// Return the precedence of \p token unless it happens to be equal to \p /// except, in which case return 0. /// \param asIdent if not null, the "as" UniqueString used to parse TS /// AsExpressions. inline unsigned getPrecedenceExcept( const Token *token, TokenKind except, UniqueString *asIdent) { const TokenKind kind = token->getKind(); #if HERMES_PARSE_TS // 'as' has the same precedence as 'in' in TS. if (LLVM_UNLIKELY(kind == TokenKind::identifier) && LLVM_UNLIKELY(token->getIdentifier() == asIdent)) { return getPrecedence(TokenKind::rw_in); } #endif return LLVM_LIKELY(kind != except) ? getPrecedence(kind) : 0; } } // namespace Optional<ESTree::Node *> JSParserImpl::parseBinaryExpression(Param param) { // The stack can never go deeper than the number of precedence levels, // unless we have a right-associative operator. // We have 10 precedence levels. constexpr unsigned STACK_SIZE = 16; struct PrecedenceStackEntry { /// Left hand side expression. ESTree::NodePtr expr; // Operator for this expression. TokenKind opKind; // Start location for the left hand side expression. SMLoc exprStartLoc; PrecedenceStackEntry( ESTree::NodePtr expr, TokenKind opKind, SMLoc exprStartLoc) : expr(expr), opKind(opKind), exprStartLoc(exprStartLoc) {} }; llvh::SmallVector<PrecedenceStackEntry, STACK_SIZE> stack{}; // True upon encountering a '??' operator. bool hasNullish = false; // True upon encountering a '&&' or '||' operator. bool hasBoolean = false; /// Allocate a binary expression node with the specified children and /// operator. const auto newBinNode = [this, &hasNullish, &hasBoolean]( ESTree::NodePtr left, TokenKind opKind, ESTree::NodePtr right, SMLoc startLoc, SMLoc endLoc) -> ESTree::NodePtr { UniqueString *opIdent = getTokenIdent(opKind); if (opKind == TokenKind::ampamp || opKind == TokenKind::pipepipe || opKind == TokenKind::questionquestion) { if ((hasNullish && opKind != TokenKind::questionquestion) || (hasBoolean && opKind == TokenKind::questionquestion)) { // This error doesn't prevent parsing the rest of the binary expression, // because it's only there to avoid confusion from the JS author's // perspective. Report the error but continue parsing. // The question marks are escaped to avoid triggering a trigraph. error( {left->getStartLoc(), right->getEndLoc()}, "Mixing '\?\?' with '&&' or '||' requires parentheses"); } if (opKind == TokenKind::questionquestion) { hasNullish = true; } else { hasBoolean = true; } return setLocation( startLoc, endLoc, new (context_) ESTree::LogicalExpressionNode(left, right, opIdent)); #if HERMES_PARSE_TS } else if (LLVM_UNLIKELY(opKind == TokenKind::identifier)) { // The only identifier used as a binary operator is 'as' in TS // and it would only have been pushed if TS parsing was enabled. return setLocation( startLoc, endLoc, new (context_) ESTree::TSAsExpressionNode(left, right)); #endif } else { return setLocation( startLoc, endLoc, new (context_) ESTree::BinaryExpressionNode(left, right, opIdent)); } }; // Decide whether to recognize "in" as a binary operator. const TokenKind exceptKind = !param.has(ParamIn) ? TokenKind::rw_in : TokenKind::none; SMLoc topExprStartLoc = tok_->getStartLoc(); auto optExpr = parseUnaryExpression(); if (!optExpr) return None; ESTree::NodePtr topExpr = optExpr.getValue(); SMLoc topExprEndLoc = getPrevTokenEndLoc(); // While the current token is a binary operator. while (unsigned precedence = getPrecedenceExcept( tok_, exceptKind, HERMES_PARSE_TS && context_.getParseTS() ? asIdent_ : nullptr)) { // If the next operator has no greater precedence than the operator on the // stack, pop the stack, creating a new binary expression. while (!stack.empty() && precedence <= getPrecedence(stack.back().opKind)) { if (precedence == getPrecedence(stack.back().opKind) && !isLeftAssoc(stack.back().opKind)) { // If the precedences are equal, then we avoid popping for // right-associative operators to allow for the entire right-associative // expression to be built from the right. break; } topExpr = newBinNode( stack.back().expr, stack.back().opKind, topExpr, stack.back().exprStartLoc, topExprEndLoc); topExprStartLoc = topExpr->getStartLoc(); stack.pop_back(); } // The next operator has a higher precedence than the previous one (or there // is no previous one). The situation looks something like this: // .... + topExpr * rightExpr .... // ^ // We are here // Push topExpr and the '*', so we can parse rightExpr. stack.emplace_back(topExpr, tok_->getKind(), topExprStartLoc); advance(); topExprStartLoc = tok_->getStartLoc(); #if HERMES_PARSE_TS if (context_.getParseTS() && LLVM_UNLIKELY(stack.back().opKind == TokenKind::identifier)) { auto optRightExpr = parseTypeAnnotationTS(); if (!optRightExpr) return None; topExpr = optRightExpr.getValue(); } else #endif { auto optRightExpr = parseUnaryExpression(); if (!optRightExpr) return None; topExpr = optRightExpr.getValue(); } topExprEndLoc = getPrevTokenEndLoc(); } // We have consumed all binary operators. Pop the stack, creating expressions. while (!stack.empty()) { topExpr = newBinNode( stack.back().expr, stack.back().opKind, topExpr, stack.back().exprStartLoc, topExprEndLoc); stack.pop_back(); } assert( stack.empty() && "Stack must be empty when done parsing binary expression"); return topExpr; } Optional<ESTree::Node *> JSParserImpl::parseConditionalExpression( Param param, CoverTypedParameters coverTypedParameters) { SMLoc startLoc = tok_->getStartLoc(); auto optTest = parseBinaryExpression(param); if (!optTest) return None; ESTree::Node *test = *optTest; if (!check(TokenKind::question)) { // No '?', so this isn't a conditional expression. // If CoverTypedParameters::Yes, we still need to account for this // being formal parameters, so try that. // TS doesn't have type casts with this syntax, but we must still // cover the typed identifier node for when it turns into an arrow function. #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if (context_.getParseTypes() && coverTypedParameters == CoverTypedParameters::Yes) { auto optCover = tryParseCoverTypedIdentifierNode(test, false); if (!optCover) return None; if (*optCover) return *optCover; } #endif // No CoverTypedParameters found, just return the LHS. return test; } ESTree::Node *consequent = nullptr; SMRange questionRange = tok_->getSourceRange(); #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if (context_.getParseTypes()) { // Save here to save the question mark (we can only save on punctuators). // Early returns will happen if we find anything that leads to // short-circuiting out of the traditional conditional expression. JSLexer::SavePoint savePoint{&lexer_}; advance(); // If CoverTypedParameters::Yes, we still need to account for this // being formal parameters, so try that, // in which case the '?' was part of an optional parameter, not a // conditional expression. if (coverTypedParameters == CoverTypedParameters::Yes) { auto optCover = tryParseCoverTypedIdentifierNode(test, true); if (!optCover) return None; if (*optCover) return *optCover; } // It is also possible to have a '?' without ':' but not be a conditional // expression, in the case of typed arrow parameters that didn't have a type // annotation. For example: // (foo?) => 1 // ^ // The tokens which can come here are limited to ',', '=', and ')'. if (coverTypedParameters == CoverTypedParameters::Yes && checkN(TokenKind::comma, TokenKind::r_paren, TokenKind::equal)) { return setLocation( startLoc, questionRange, new (context_) ESTree::CoverTypedIdentifierNode(test, nullptr, true)); } // Now we're in the real backtracking stage. // First, parse with AllowTypedArrowFunction::Yes to allow for the // possibility of a concise arrow function with return types. However, we // want to avoid the possibility of eating the ':' that we'll need for the // conditional expression's alternate. For example: // a ? b1 => (c1) : b2 => (c2) // We want to account for b2 incorrectly being parsed as the returnType // of an arrow function returned by the arrow function with param b1. // Thus, after parsing with AllowTypedArrowFunction::Yes, we check to // see if there is a ':' afterwards. If there isn't, failure is assured, // so we restore to the '?' and try again below, with // AllowTypedArrowFunction::No. SourceErrorManager::SaveAndSuppressMessages suppress{ &sm_, Subsystem::Parser}; CHECK_RECURSION; auto optConsequent = parseAssignmentExpression( ParamIn, AllowTypedArrowFunction::Yes, CoverTypedParameters::No); if (optConsequent && check(TokenKind::colon)) { consequent = *optConsequent; } else { // Parsing with typed arrow functions failed because we don't have a :, // so reset and try again. savePoint.restore(); } } #endif // Only try with AllowTypedArrowFunction::No if we haven't already set // up the consequent using AllowTypedArrowFunction::Yes. if (!consequent) { // Consume the '?' (either for the first time or after savePoint.restore()). advance(); CHECK_RECURSION; auto optConsequent = parseAssignmentExpression( ParamIn, AllowTypedArrowFunction::No, CoverTypedParameters::No); if (!optConsequent) return None; consequent = *optConsequent; } if (!eat( TokenKind::colon, JSLexer::AllowRegExp, "in conditional expression after '... ? ...'", "location of '?'", questionRange.Start)) return None; auto optAlternate = parseAssignmentExpression( param, AllowTypedArrowFunction::Yes, CoverTypedParameters::No); if (!optAlternate) return None; ESTree::Node *alternate = *optAlternate; return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ConditionalExpressionNode(test, alternate, consequent)); } #if HERMES_PARSE_FLOW || HERMES_PARSE_TS Optional<ESTree::Node *> JSParserImpl::tryParseCoverTypedIdentifierNode( ESTree::Node *test, bool optional) { assert(context_.getParseTypes() && "must be parsing types"); // In the case of flow types in arrow function parameters, we may have // optional parameters which look like: // Identifier ? : TypeAnnotation // Because the colon and the type annotation are optional, we check and // consume the colon here and return a CoverTypedIdentifierNode if it's // possible we are parsing typed arrow parameters. if (check(TokenKind::colon) && test->getParens() == 0) { if (isa<ESTree::IdentifierNode>(test) || isa<ESTree::ObjectExpressionNode>(test) || isa<ESTree::ArrayExpressionNode>(test)) { // Deliberately wrap the type annotation later when reparsing. SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optRet = parseTypeAnnotation(annotStart); if (!optRet) return None; ESTree::Node *type = *optRet; return setLocation( test, getPrevTokenEndLoc(), new (context_) ESTree::CoverTypedIdentifierNode(test, type, optional)); } // The colon must indicate something another than the typeAnnotation for // the parameter. Continue as usual. } return nullptr; } #endif Optional<ESTree::YieldExpressionNode *> JSParserImpl::parseYieldExpression( Param param) { assert( paramYield_ && check(TokenKind::rw_yield, TokenKind::identifier) && tok_->getResWordOrIdentifier() == yieldIdent_ && "yield expression must start with 'yield'"); SMRange yieldLoc = advance(); if (check(TokenKind::semi) || checkEndAssignmentExpression()) return setLocation( yieldLoc, yieldLoc, new (context_) ESTree::YieldExpressionNode(nullptr, false)); bool delegate = checkAndEat(TokenKind::star); auto optArg = parseAssignmentExpression( param.get(ParamIn), AllowTypedArrowFunction::Yes, CoverTypedParameters::No); if (!optArg) return None; return setLocation( yieldLoc, getPrevTokenEndLoc(), new (context_) ESTree::YieldExpressionNode(optArg.getValue(), delegate)); } Optional<ESTree::ClassDeclarationNode *> JSParserImpl::parseClassDeclaration( Param param) { assert(check(TokenKind::rw_class) && "class must start with 'class'"); // NOTE: Class definition is always strict mode code. SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; setStrictMode(true); SMLoc startLoc = advance().Start; ESTree::Node *name = nullptr; ESTree::Node *typeParams = nullptr; if (check(TokenKind::identifier)) { auto optName = parseBindingIdentifier(Param{}); if (!optName) { errorExpected( TokenKind::identifier, "in class declaration", "location of 'class'", startLoc); return None; } name = *optName; } else if (!param.has(ParamDefault)) { // Identifier is required unless we have +Default parameter. errorExpected( TokenKind::identifier, "after 'class'", "location of 'class'", startLoc); return None; } #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::less)) { auto optParams = parseTypeParamsFlow(); if (!optParams) return None; typeParams = *optParams; } #endif #if HERMES_PARSE_TS if (context_.getParseTS() && check(TokenKind::less)) { auto optParams = parseTSTypeParameters(); if (!optParams) return None; typeParams = *optParams; } #endif auto optClass = parseClassTail(startLoc, name, typeParams, ClassParseKind::Declaration); if (!optClass) return None; return llvh::cast<ESTree::ClassDeclarationNode>(*optClass); } Optional<ESTree::ClassExpressionNode *> JSParserImpl::parseClassExpression() { assert(check(TokenKind::rw_class) && "class must start with 'class'"); // NOTE: A class definition is always strict mode code. SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; setStrictMode(true); SMLoc start = advance().Start; ESTree::Node *name = nullptr; ESTree::Node *typeParams = nullptr; if (!check(TokenKind::rw_extends, TokenKind::l_brace) && !(context_.getParseFlow() && check(TokenKind::rw_implements, TokenKind::less)) && !(context_.getParseTS() && check(TokenKind::less))) { // Try to parse a BindingIdentifier if we did not see a ClassHeritage // or a '{'. auto optName = parseBindingIdentifier(Param{}); if (!optName) { errorExpected( TokenKind::identifier, "in class expression", "location of 'class'", start); return None; } name = *optName; } #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::less)) { auto optParams = parseTypeParamsFlow(); if (!optParams) return None; typeParams = *optParams; } #endif #if HERMES_PARSE_TS if (context_.getParseTS() && check(TokenKind::less)) { auto optParams = parseTSTypeParameters(); if (!optParams) return None; typeParams = *optParams; } #endif auto optClass = parseClassTail(start, name, typeParams, ClassParseKind::Expression); if (!optClass) return None; return llvh::cast<ESTree::ClassExpressionNode>(*optClass); } Optional<ESTree::Node *> JSParserImpl::parseClassTail( SMLoc startLoc, ESTree::Node *name, ESTree::Node *typeParams, ClassParseKind kind) { ESTree::Node *superClass = nullptr; ESTree::Node *superTypeParams = nullptr; if (checkAndEat(TokenKind::rw_extends)) { // ClassHeritage[opt] { ClassBody[opt] } // ^ auto optSuperClass = parseLeftHandSideExpression(); if (!optSuperClass) return None; superClass = *optSuperClass; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::less)) { auto optParams = parseTypeArgsFlow(); if (!optParams) return None; superTypeParams = *optParams; } #endif #if HERMES_PARSE_TS if (context_.getParseTS() && check(TokenKind::less)) { auto optParams = parseTSTypeArguments(); if (!optParams) return None; superTypeParams = *optParams; } #endif } ESTree::NodeList implements{}; #if HERMES_PARSE_FLOW if (context_.getParseFlow()) { if (checkAndEat(TokenKind::rw_implements) || checkAndEat(implementsIdent_)) { while (!check(TokenKind::l_brace)) { if (!need( TokenKind::identifier, "in class 'implements'", "start of class", startLoc)) return None; auto optImpl = parseClassImplementsFlow(); if (!optImpl) return None; implements.push_back(**optImpl); if (!checkAndEat(TokenKind::comma)) { break; } } } } #endif if (!need( TokenKind::l_brace, "in class definition", "start of class", startLoc)) { return None; } auto optBody = parseClassBody(startLoc); if (!optBody) return None; if (kind == ClassParseKind::Declaration) { return setLocation( startLoc, *optBody, new (context_) ESTree::ClassDeclarationNode( name, typeParams, superClass, superTypeParams, std::move(implements), {}, *optBody)); } return setLocation( startLoc, *optBody, new (context_) ESTree::ClassExpressionNode( name, typeParams, superClass, superTypeParams, std::move(implements), {}, *optBody)); } Optional<ESTree::ClassBodyNode *> JSParserImpl::parseClassBody(SMLoc startLoc) { assert(check(TokenKind::l_brace) && "class body must begin with '{'"); SMLoc braceLoc = advance().Start; // It is a Syntax Error if PrototypePropertyNameList of ClassElementList // contains more than one occurrence of "constructor". ESTree::Node *constructor = nullptr; ESTree::NodeList body{}; while (!check(TokenKind::r_brace)) { bool isStatic = false; SMRange startRange = tok_->getSourceRange(); bool declare = false; bool isPrivate = false; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(declareIdent_)) { // Check for "declare" class properties. auto optNext = lexer_.lookahead1(llvh::None); if (optNext.hasValue() && (*optNext == TokenKind::rw_static || *optNext == TokenKind::identifier || *optNext == TokenKind::plus || *optNext == TokenKind::minus)) { declare = true; advance(); } } #endif #if HERMES_PARSE_TS if (context_.getParseTS() && check(TokenKind::rw_private)) { // Check for "private" class properties. auto optNext = lexer_.lookahead1(llvh::None); if (optNext.hasValue() && (*optNext == TokenKind::rw_static || *optNext == TokenKind::identifier)) { isPrivate = true; advance(); } } #endif switch (tok_->getKind()) { case TokenKind::semi: advance(); break; case TokenKind::rw_static: // static MethodDefinition // static FieldDefinition isStatic = true; advance(); // intentional fallthrough default: { // ClassElement auto optElem = parseClassElement(isStatic, startRange, declare, isPrivate); if (!optElem) return None; if (auto *method = dyn_cast<ESTree::MethodDefinitionNode>(*optElem)) { if (method->_kind == constructorIdent_) { if (constructor) { // Cannot have duplicate constructors, but report the error // and move on to parse the rest of the class. error( method->getSourceRange(), "duplicate constructors in class"); sm_.note( constructor->getSourceRange(), "first constructor definition", Subsystem::Parser); } else { constructor = method; } } } else if (auto *prop = dyn_cast<ESTree::ClassPropertyNode>(*optElem)) { if (auto *propId = dyn_cast<ESTree::IdentifierNode>(prop->_key)) { if (propId->_name == constructorIdent_) { error(prop->getSourceRange(), "invalid class property name"); } } else if ( auto *propStr = dyn_cast<ESTree::StringLiteralNode>(prop->_key)) { if (propStr->_value == constructorIdent_) { error(prop->getSourceRange(), "invalid class property name"); } } } body.push_back(**optElem); } } } if (!need( TokenKind::r_brace, "at end of class definition", "start of class", startLoc)) { return None; } return setLocation( braceLoc, advance().End, new (context_) ESTree::ClassBodyNode(std::move(body))); } Optional<ESTree::Node *> JSParserImpl::parseClassElement( bool isStatic, SMRange startRange, bool declare, bool isPrivate, bool eagerly) { SMLoc startLoc = tok_->getStartLoc(); bool optional = false; enum class SpecialKind { None, Get, Set, Generator, Async, AsyncGenerator, ClassProperty, }; // Indicates if this method is out of the ordinary. // In particular, indicates getters and setters. SpecialKind special = SpecialKind::None; // When true, call parsePropertyName. // Set to false if the identifiers 'get' or 'set' were already parsed as // function names instead of as getter/setter specifiers. bool doParsePropertyName = true; ESTree::Node *prop = nullptr; if (check(getIdent_)) { SMRange range = advance(); if (!checkN( TokenKind::less, TokenKind::l_paren, TokenKind::r_brace, TokenKind::equal, TokenKind::colon, TokenKind::semi)) { // This was actually a getter. special = SpecialKind::Get; } else { prop = setLocation( range, range, new (context_) ESTree::IdentifierNode(getIdent_, nullptr, false)); doParsePropertyName = false; } } else if (check(setIdent_)) { SMRange range = advance(); if (!checkN( TokenKind::less, TokenKind::l_paren, TokenKind::r_brace, TokenKind::equal, TokenKind::colon, TokenKind::semi)) { // If we don't see '(' then this was actually a setter. special = SpecialKind::Set; } else { prop = setLocation( range, range, new (context_) ESTree::IdentifierNode(setIdent_, nullptr, false)); doParsePropertyName = false; } } else if (check(asyncIdent_)) { SMRange range = advance(); if (!checkN( TokenKind::less, TokenKind::l_paren, TokenKind::r_brace, TokenKind::equal, TokenKind::colon, TokenKind::semi) && !lexer_.isNewLineBeforeCurrentToken()) { // If we don't see '(' then this was actually an async method. // Async methods cannot have a newline between 'async' and the name. // These can be either Async or AsyncGenerator, so check for that. special = checkAndEat(TokenKind::star) ? SpecialKind::AsyncGenerator : SpecialKind::Async; } else { prop = setLocation( range, range, new (context_) ESTree::IdentifierNode(asyncIdent_, nullptr, false)); doParsePropertyName = false; } } else if (checkAndEat(TokenKind::star)) { special = SpecialKind::Generator; } else if (check(TokenKind::l_paren, TokenKind::less) && isStatic) { // We've already parsed 'static', but there is nothing between 'static' // and the '(', so it must be used as the PropertyName and not as an // indicator for a static function. prop = setLocation( startRange, startRange, new (context_) ESTree::IdentifierNode(staticIdent_, nullptr, false)); isStatic = false; doParsePropertyName = false; } ESTree::Node *variance = nullptr; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::plus, TokenKind::minus)) { variance = setLocation( tok_, tok_, new (context_) ESTree::VarianceNode( check(TokenKind::plus) ? plusIdent_ : minusIdent_)); advance(JSLexer::GrammarContext::Type); } #endif bool computed = false; if (doParsePropertyName) { if (check(TokenKind::private_identifier)) { isPrivate = true; prop = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getPrivateIdentifier(), nullptr, false)); advance(); } else { computed = check(TokenKind::l_square); auto optProp = parsePropertyName(); if (!optProp) return None; prop = *optProp; } } // Store the propName for comparisons, used for SyntaxErrors. UniqueString *propName = nullptr; if (auto *id = dyn_cast<ESTree::IdentifierNode>(prop)) { propName = id->_name; } else if (auto *str = dyn_cast<ESTree::StringLiteralNode>(prop)) { propName = str->_value; } bool isConstructor = !isStatic && !computed && propName && propName->str() == "constructor"; if (special == SpecialKind::None && !check(TokenKind::less, TokenKind::l_paren)) { // Parse a class property, because this can't be a method definition. // Attempt ASI after the fact, and continue on, letting the next iteration // error if it wasn't actually a class property. // FieldDefinition ; // ^ ESTree::Node *typeAnnotation = nullptr; #if HERMES_PARSE_TS if (context_.getParseTS() && checkAndEat(TokenKind::question)) { optional = true; } #endif #if HERMES_PARSE_FLOW || HERMES_PARSE_TS if (context_.getParseTypes() && check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotation(annotStart); if (!optType) return None; typeAnnotation = *optType; } #endif ESTree::Node *value = nullptr; if (checkAndEat(TokenKind::equal)) { // ClassElementName Initializer[opt] // ^ auto optValue = parseAssignmentExpression(); if (!optValue) return None; value = *optValue; if (declare) { error(startRange, "Invalid 'declare' with initializer"); } } // ASI is allowed for separating class elements. if (!eatSemi(true) && !typeAnnotation) { errorExpected( TokenKind::semi, "after class property", "start of class property", startRange.Start); return None; } if (isPrivate) { return setLocation( prop, getPrevTokenEndLoc(), new (context_) ESTree::ClassPrivatePropertyNode( prop, value, isStatic, declare, optional, variance, typeAnnotation)); } return setLocation( startRange, getPrevTokenEndLoc(), new (context_) ESTree::ClassPropertyNode( prop, value, computed, isStatic, declare, optional, variance, typeAnnotation)); } if (declare) { error(startRange, "Invalid 'declare' in class method"); } SMLoc funcExprStartLoc = tok_->getStartLoc(); ESTree::Node *typeParams = nullptr; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::less)) { auto optTypeParams = parseTypeParamsFlow(); if (!optTypeParams) return None; typeParams = *optTypeParams; } #endif // ( if (!need( TokenKind::l_paren, "in method definition", "start of method definition", startLoc)) return None; ESTree::NodeList args{}; llvh::SaveAndRestore<bool> saveArgsAndBodyParamYield( paramYield_, special == SpecialKind::Generator || special == SpecialKind::AsyncGenerator); llvh::SaveAndRestore<bool> saveArgsAndBodyParamAwait( paramAwait_, special == SpecialKind::Async || special == SpecialKind::AsyncGenerator); if (!parseFormalParameters(Param{}, args)) return None; ESTree::Node *returnType = nullptr; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optRet = parseTypeAnnotationFlow(annotStart); if (!optRet) return None; returnType = *optRet; } #endif if (!need( TokenKind::l_brace, "in method definition", "start of method definition", startLoc)) return None; auto optBody = parseFunctionBody( ParamReturn, eagerly, saveArgsAndBodyParamYield.get(), saveArgsAndBodyParamAwait.get(), JSLexer::AllowRegExp, true); if (!optBody) return None; auto *funcExpr = setLocation( funcExprStartLoc, optBody.getValue(), new (context_) ESTree::FunctionExpressionNode( nullptr, std::move(args), optBody.getValue(), typeParams, returnType, /* predicate */ nullptr, special == SpecialKind::Generator || special == SpecialKind::AsyncGenerator, special == SpecialKind::Async || special == SpecialKind::AsyncGenerator)); assert(isStrictMode() && "parseClassElement should only be used for classes"); funcExpr->isMethodDefinition = true; if (special == SpecialKind::Get && funcExpr->_params.size() != 0) { error( {startLoc, funcExpr->getEndLoc()}, Twine("getter method must no one formal arguments, found ") + Twine(funcExpr->_params.size())); } if (special == SpecialKind::Set && funcExpr->_params.size() != 1) { error( {startLoc, funcExpr->getEndLoc()}, Twine("setter method must have exactly one formal argument, found ") + Twine(funcExpr->_params.size())); } #if HERMES_PARSE_FLOW if ((special == SpecialKind::Get || special == SpecialKind::Set) && typeParams != nullptr) { error( {startLoc, funcExpr->getEndLoc()}, "accessor method may not have type parameters"); } #endif if (isStatic && propName && propName->str() == "prototype") { // ClassElement : static MethodDefinition // It is a Syntax Error if PropName of MethodDefinition is "prototype". error( {startLoc, funcExpr->getEndLoc()}, "prototype method must not be static"); return None; } UniqueString *kind = methodIdent_; if (isConstructor) { if (special != SpecialKind::None) { // It is a Syntax Error if PropName of MethodDefinition is "constructor" // and SpecialMethod of MethodDefinition is true. // TODO: Account for generator methods in SpecialMethod here. error( {startLoc, funcExpr->getEndLoc()}, "constructor method must not be a getter or setter"); return None; } kind = constructorIdent_; } else if (special == SpecialKind::Get) { kind = getIdent_; } else if (special == SpecialKind::Set) { kind = setIdent_; } if (isPrivate) { prop = setLocation( startLoc, prop, new (context_) ESTree::PrivateNameNode(prop)); } if (variance) { error(variance->getSourceRange(), "Unexpected variance sigil"); } return setLocation( startRange, optBody.getValue(), new (context_) ESTree::MethodDefinitionNode( prop, funcExpr, kind, computed, isStatic)); } bool JSParserImpl::reparseArrowParameters( ESTree::Node *node, ESTree::NodeList &paramList, bool &isAsync) { // Empty argument list "()". if (node->getParens() == 0 && isa<ESTree::CoverEmptyArgsNode>(node)) return true; // A single identifier without parens. if (node->getParens() == 0 && isa<ESTree::IdentifierNode>(node)) { paramList.push_back(*node); return validateBindingIdentifier( Param{}, node->getSourceRange(), cast<ESTree::IdentifierNode>(node)->_name, TokenKind::identifier); } ESTree::NodeList nodeList{}; if (auto *callNode = dyn_cast<ESTree::CallExpressionNode>(node)) { // Async function parameters look like call expressions. For example: // async(x,y) // It must have no surrounding parens and the name must be 'async'. // It must also not already be `async`, because the CallExpression // determines whether it is `async`. // Set `isAsync = true` to indicate that this was async. auto *callee = dyn_cast<ESTree::IdentifierNode>(callNode->_callee); if (!isAsync && callNode->getParens() == 0 && callee && callee->_name == asyncIdent_) { nodeList = std::move(callNode->_arguments); isAsync = true; } else { error(node->getSourceRange(), "invalid arrow function parameter list"); return false; } } else { if (node->getParens() != 1) { error(node->getSourceRange(), "invalid arrow function parameter list"); return false; } if (auto *seqNode = dyn_cast<ESTree::SequenceExpressionNode>(node)) { nodeList = std::move(seqNode->_expressions); } else { node->clearParens(); nodeList.push_back(*node); } } llvh::SaveAndRestore<bool> oldParamAwait(paramAwait_, paramAwait_ || isAsync); // If the node has 0 parentheses, return true, otherwise print an error and // return false. auto checkParens = [this](ESTree::Node *n) { if (n->getParens() == 0) return true; error(n->getSourceRange(), "parentheses are not allowed around parameters"); return false; }; for (auto it = nodeList.begin(), e = nodeList.end(); it != e;) { auto *expr = &*it; it = nodeList.erase(it); if (!checkParens(expr)) continue; if (auto *CRE = dyn_cast<ESTree::CoverRestElementNode>(expr)) { if (it != e) error(expr->getSourceRange(), "rest parameter must be last"); else paramList.push_back(*CRE->_rest); continue; } if (auto *spread = dyn_cast<ESTree::SpreadElementNode>(expr)) { // async arrow heads are initially parsed as CallExpression, // which means that Rest elements are parsed as SpreadElement. if (it != e) error(expr->getSourceRange(), "rest parameter must be last"); else paramList.push_back(*new (context_) ESTree::RestElementNode(spread->_argument)); continue; } if (isa<ESTree::CoverTrailingCommaNode>(expr)) { assert( it == e && "CoverTrailingCommaNode should have been only parsed last"); // Just skip it. continue; } ESTree::AssignmentExpressionNode *asn = nullptr; ESTree::Node *init = nullptr; // If we encounter an initializer, unpack it. if ((asn = dyn_cast<ESTree::AssignmentExpressionNode>(expr))) { if (asn->_operator == getTokenIdent(TokenKind::equal)) { expr = asn->_left; init = asn->_right; if (!checkParens(expr)) continue; } } auto optParam = reparseAssignmentPattern(expr, true); if (!optParam) continue; expr = *optParam; if (init) { expr = setLocation( asn, asn, new (context_) ESTree::AssignmentPatternNode(expr, init)); } if (auto *ident = dyn_cast<ESTree::IdentifierNode>(expr)) { validateBindingIdentifier( Param{}, ident->getSourceRange(), ident->_name, TokenKind::identifier); } paramList.push_back(*expr); } return true; } Optional<ESTree::Node *> JSParserImpl::parseArrowFunctionExpression( Param param, ESTree::Node *leftExpr, ESTree::Node *typeParams, ESTree::Node *returnType, ESTree::Node *predicate, SMLoc startLoc, AllowTypedArrowFunction allowTypedArrowFunction, bool forceAsync) { // ArrowFunction : ArrowParameters [no line terminator] => ConciseBody. assert( check(TokenKind::equalgreater) && !lexer_.isNewLineBeforeCurrentToken() && "ArrowFunctionExpression expects [no new line] '=>'"); llvh::SaveAndRestore<bool> argsParamAwait(paramAwait_, forceAsync); if (!eat( TokenKind::equalgreater, JSLexer::GrammarContext::AllowRegExp, "in arrow function expression", "start of arrow function", startLoc)) return None; bool isAsync = forceAsync; ESTree::NodeList paramList; if (!reparseArrowParameters(leftExpr, paramList, isAsync)) return None; SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; ESTree::Node *body; bool expression; llvh::SaveAndRestore<bool> oldParamYield(paramYield_, false); llvh::SaveAndRestore<bool> bodyParamAwait(paramAwait_, isAsync); if (check(TokenKind::l_brace)) { auto optBody = parseFunctionBody( Param{}, true, oldParamYield.get(), argsParamAwait.get(), JSLexer::AllowDiv, true); if (!optBody) return None; body = *optBody; expression = false; } else { auto optConcise = parseAssignmentExpression( param.get(ParamIn), allowTypedArrowFunction, CoverTypedParameters::No, nullptr); if (!optConcise) return None; body = *optConcise; expression = true; } auto *arrow = new (context_) ESTree::ArrowFunctionExpressionNode( nullptr, std::move(paramList), body, typeParams, returnType, predicate, expression, isAsync); return setLocation(startLoc, getPrevTokenEndLoc(), arrow); } Optional<ESTree::Node *> JSParserImpl::reparseAssignmentPattern( ESTree::Node *node, bool inDecl) { if (!node->getParens()) { if (auto *AEN = dyn_cast<ESTree::ArrayExpressionNode>(node)) { return reparseArrayAsignmentPattern(AEN, inDecl); } if (auto *OEN = dyn_cast<ESTree::ObjectExpressionNode>(node)) { return reparseObjectAssignmentPattern(OEN, inDecl); } if (auto *ident = dyn_cast<ESTree::IdentifierNode>(node)) { // Validate in this function to avoid validating in each branch of // the conditions within the other reparse functions. // Validation does not prevent progress of the parse here, so we can // return node regardless of whether we failed to validate. validateBindingIdentifier( Param{}, ident->getSourceRange(), ident->_name, TokenKind::identifier); return node; } if (isa<ESTree::PatternNode>(node)) { // Pattern nodes validate their binding identifiers when they are // initially parsed, no work to do here. return node; } #if HERMES_PARSE_FLOW if (auto *cover = dyn_cast<ESTree::CoverTypedIdentifierNode>(node)) { auto optAssn = reparseAssignmentPattern(cover->_left, inDecl); // type may be nullptr, but the patterns may have null typeAnnotation. auto *type = cover->_right; if (!optAssn) return None; if (auto *apn = dyn_cast<ESTree::ArrayPatternNode>(*optAssn)) { apn->_typeAnnotation = type; return setLocation(cover, cover, apn); } if (auto *opn = dyn_cast<ESTree::ObjectPatternNode>(*optAssn)) { opn->_typeAnnotation = type; return setLocation(cover, cover, opn); } if (auto *id = dyn_cast<ESTree::IdentifierNode>(*optAssn)) { id->_typeAnnotation = type; id->_optional = cover->_optional; return setLocation(cover, cover, id); } } if (auto *typecast = dyn_cast<ESTree::TypeCastExpressionNode>(node)) { auto optAssn = reparseAssignmentPattern(typecast->_expression, inDecl); auto *type = typecast->_typeAnnotation; if (!optAssn) return None; if (auto *apn = dyn_cast<ESTree::ArrayPatternNode>(*optAssn)) { apn->_typeAnnotation = type; return setLocation(apn, type, apn); } if (auto *opn = dyn_cast<ESTree::ObjectPatternNode>(*optAssn)) { opn->_typeAnnotation = type; return setLocation(opn, type, opn); } if (auto *id = dyn_cast<ESTree::IdentifierNode>(*optAssn)) { id->_typeAnnotation = type; return setLocation(id, type, id); } } #endif } if (inDecl) { error(node->getSourceRange(), "identifier or pattern expected"); return None; } return node; } Optional<ESTree::Node *> JSParserImpl::reparseArrayAsignmentPattern( ESTree::ArrayExpressionNode *AEN, bool inDecl) { ESTree::NodeList elements{}; for (auto it = AEN->_elements.begin(), e = AEN->_elements.end(); it != e;) { ESTree::Node *elem = &*it++; AEN->_elements.remove(*elem); // Every element in the array assignment pattern is optional, // because we can parse the Elision production. if (isa<ESTree::EmptyNode>(elem)) { elements.push_back(*elem); continue; } if (auto *spread = dyn_cast<ESTree::SpreadElementNode>(elem)) { if (it != e || AEN->_trailingComma) { error(spread->getSourceRange(), "rest element must be last"); continue; } auto optSubPattern = reparseAssignmentPattern(spread->_argument, inDecl); if (!optSubPattern) continue; elem = setLocation( spread, spread, new (context_) ESTree::RestElementNode(*optSubPattern)); } else { ESTree::AssignmentExpressionNode *asn = nullptr; ESTree::Node *init = nullptr; // If we encounter an initializer, unpack it. if (!elem->getParens()) { if ((asn = dyn_cast<ESTree::AssignmentExpressionNode>(elem))) { if (asn->_operator == getTokenIdent(TokenKind::equal)) { elem = asn->_left; init = asn->_right; } } } // Reparse {...} or [...] auto optSubPattern = reparseAssignmentPattern(elem, inDecl); if (!optSubPattern) continue; elem = *optSubPattern; if (init) { elem = setLocation( asn, asn, new (context_) ESTree::AssignmentPatternNode(elem, init)); } } elements.push_back(*elem); } return setLocation( AEN->getStartLoc(), AEN->getEndLoc(), new (context_) ESTree::ArrayPatternNode(std::move(elements), nullptr)); } Optional<ESTree::Node *> JSParserImpl::reparseObjectAssignmentPattern( ESTree::ObjectExpressionNode *OEN, bool inDecl) { ESTree::NodeList elements{}; for (auto it = OEN->_properties.begin(), e = OEN->_properties.end(); it != e;) { auto *node = &*it++; OEN->_properties.remove(*node); if (auto *spread = dyn_cast<ESTree::SpreadElementNode>(node)) { if (it != e) { error(spread->getSourceRange(), "rest property must be last"); continue; } // NOTE: the spec says that AssignmentRestProperty cannot be another // pattern (see // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-destructuring-assignment-static-semantics-early-errors) // even though it would be logical. #if 0 auto optSubPattern = reparseAssignmentPattern(spread->_argument, inDecl); if (!optSubPattern) continue; node = *optSubPattern; #else node = spread->_argument; if (inDecl) { if (!isa<ESTree::IdentifierNode>(node)) { error( node->getSourceRange(), "identifier expected in parameter list"); continue; } } #endif node = setLocation( spread, spread, new (context_) ESTree::RestElementNode(node)); } else { auto *propNode = cast<ESTree::PropertyNode>(node); if (propNode->_kind != initIdent_) { error( SourceErrorManager::combineIntoRange( propNode->getStartLoc(), propNode->_key->getStartLoc()), "invalid destructuring target"); continue; } ESTree::Node *value = propNode->_value; ESTree::Node *init = nullptr; SMLoc endLoc = value->getEndLoc(); // If we encounter an initializer, unpack it. if (auto *asn = dyn_cast<ESTree::AssignmentExpressionNode>(value)) { if (asn->_operator == getTokenIdent(TokenKind::equal)) { value = asn->_left; init = asn->_right; } } else if ( auto *coverInitializer = dyn_cast<ESTree::CoverInitializerNode>(value)) { assert( isa<ESTree::IdentifierNode>(propNode->_key) && "CoverInitializedName must start with an identifier"); // Clone the key. auto *ident = cast<ESTree::IdentifierNode>(propNode->_key); value = new (context_) ESTree::IdentifierNode( ident->_name, ident->_typeAnnotation, ident->_optional); value->copyLocationFrom(propNode->_key); init = coverInitializer->_init; } // Reparse {...} or [...] auto optSubPattern = reparseAssignmentPattern(value, inDecl); if (!optSubPattern) continue; value = *optSubPattern; // If we have an initializer, create an AssignmentPattern. if (init) { value = setLocation( value, endLoc, new (context_) ESTree::AssignmentPatternNode(value, init)); } propNode->_value = value; } elements.push_back(*node); } auto *OP = new (context_) ESTree::ObjectPatternNode(std::move(elements), nullptr); OP->copyLocationFrom(OEN); return OP; } #if HERMES_PARSE_FLOW Optional<ESTree::Node *> JSParserImpl::tryParseTypedAsyncArrowFunction( Param param) { assert(context_.getParseFlow()); assert(check(asyncIdent_)); JSLexer::SavePoint savePoint{&lexer_}; SMLoc start = advance().Start; ESTree::Node *leftExpr = nullptr; ESTree::Node *typeParams = nullptr; ESTree::Node *returnType = nullptr; ESTree::Node *predicate = nullptr; { SourceErrorManager::SaveAndSuppressMessages suppress{ &sm_, Subsystem::Parser}; if (check(TokenKind::less)) { auto optTypeParams = parseTypeParamsFlow(); if (!optTypeParams) { savePoint.restore(); return None; } typeParams = *optTypeParams; } if (!check(TokenKind::l_paren)) { savePoint.restore(); return None; } auto optLeftExpr = parseConditionalExpression(param, CoverTypedParameters::Yes); if (!optLeftExpr) { savePoint.restore(); return None; } leftExpr = *optLeftExpr; if (check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; if (!check(checksIdent_)) { auto optType = parseTypeAnnotationFlow(annotStart, AllowAnonFunctionType::No); if (!optType) { savePoint.restore(); return None; } returnType = *optType; } if (check(checksIdent_)) { auto optPredicate = parsePredicateFlow(); if (!optPredicate) { savePoint.restore(); return None; } predicate = *optPredicate; } } if (!check(TokenKind::equalgreater)) { savePoint.restore(); return None; } } return parseArrowFunctionExpression( param, leftExpr, typeParams, returnType, predicate, start, AllowTypedArrowFunction::Yes, /* forceAsync */ true); } #endif Optional<ESTree::Node *> JSParserImpl::parseAssignmentExpression( Param param, AllowTypedArrowFunction allowTypedArrowFunction, CoverTypedParameters coverTypedParameters, ESTree::Node *typeParams) { struct State { SMLoc leftStartLoc = {}; Optional<ESTree::Node *> optLeftExpr = llvh::None; UniqueString *op = nullptr; SMLoc debugLoc = {}; explicit State() {} }; auto parseHelper = [this]( State &state, Param param, AllowTypedArrowFunction allowTypedArrowFunction, CoverTypedParameters coverTypedParameters, ESTree::Node *typeParams) -> Optional<ESTree::Node *> { // Check for yield, which may be lexed as a reserved word, but only in // strict mode. if (paramYield_ && check(TokenKind::rw_yield, TokenKind::identifier) && tok_->getResWordOrIdentifier() == yieldIdent_) { auto optYieldExpr = parseYieldExpression(param.get(ParamIn)); if (!optYieldExpr) return None; ESTree::YieldExpressionNode *yieldExpr = *optYieldExpr; if (yieldExpr->_argument && !checkEndAssignmentExpression()) { error(tok_->getStartLoc(), "unexpected token after yield expression"); return None; } return yieldExpr; } SMLoc startLoc = tok_->getStartLoc(); bool forceAsync = false; if (check(asyncIdent_)) { OptValue<TokenKind> optNext = lexer_.lookahead1(TokenKind::identifier); if (optNext.hasValue() && *optNext == TokenKind::identifier) { forceAsync = true; } #if HERMES_PARSE_FLOW if (context_.getParseFlow() && optNext.hasValue() && (*optNext == TokenKind::less || *optNext == TokenKind::l_paren)) { auto optAsyncArrow = tryParseTypedAsyncArrowFunction(param); if (optAsyncArrow.hasValue()) { return *optAsyncArrow; } } #endif } #if HERMES_PARSE_FLOW if (context_.getParseFlow() && allowTypedArrowFunction == AllowTypedArrowFunction::Yes && !typeParams && check(TokenKind::less)) { JSLexer::SavePoint savePoint{&lexer_}; // Suppress messages from the parser while still displaying lexer // messages. CollectMessagesRAII collect{&sm_, true}; // Do as the flow parser does due to JSX ambiguities. // First we try and parse as an assignment expression disallowing // typed arrow functions. If that fails, then try again while allowing // typed arrow functions and attach the type parameters after the fact. auto optAssign = parseAssignmentExpression( param, AllowTypedArrowFunction::No, CoverTypedParameters::No, nullptr); if (optAssign) { // That worked, so just return it directly. collect.setDiscardMessages(false); return *optAssign; } else { // Consume the type parameters and try again. savePoint.restore(); auto optTypeParams = parseTypeParamsFlow(); // Type parameters must be followed by a '(' to be meaningful. if (optTypeParams && check(TokenKind::l_paren)) { typeParams = *optTypeParams; optAssign = parseAssignmentExpression( param, AllowTypedArrowFunction::Yes, CoverTypedParameters::No, typeParams); if (optAssign) { // We've got the arrow function now, return it directly. return *optAssign; } else { // That's everything we can try. error( typeParams->getSourceRange(), "type parameters must be used in an arrow function expression"); return None; } } else { // Invalid type params, and also invalid JSX. Bail. savePoint.restore(); } } } #endif state.leftStartLoc = tok_->getStartLoc(); state.optLeftExpr = parseConditionalExpression(param, coverTypedParameters); if (!state.optLeftExpr) return None; ESTree::Node *returnType = nullptr; ESTree::Node *predicate = nullptr; #if HERMES_PARSE_FLOW if (context_.getParseFlow()) { if (allowTypedArrowFunction == AllowTypedArrowFunction::Yes && ((*state.optLeftExpr)->getParens() != 0 || isa<ESTree::CoverEmptyArgsNode>(*state.optLeftExpr)) && check(TokenKind::colon)) { JSLexer::SavePoint savePoint{&lexer_}; // Defer our decision on whether to show or suppress messages for this // next section. // If we are unsuccessful during the parse, it can mean that we need to // start parsing JSX children inside tags, instead of function type // parameters. We need to suppress lexer messages because the lexing // rules inside JSX are quite different from JS/Flow. For example: x ? // (1) : <tag>#{foo}</tag>; // ^ // and // x ? (1) : <tag>"</tag>; // ^ // must be able to handle the lexer errors that would occur if we lexed // the inside of the JSX tags as JS. CollectMessagesRAII collect{&sm_, true}; SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; bool startsWithPredicate = check(checksIdent_); auto optType = startsWithPredicate ? llvh::None : parseTypeAnnotationFlow(annotStart, AllowAnonFunctionType::No); if (optType) returnType = *optType; if (optType || startsWithPredicate) { if (check(TokenKind::equalgreater)) { assert( !startsWithPredicate && "no returnType if startsWithPredicate"); // Done parsing the return type and predicate. // Successful parse, show any messages that the lexer emitted. collect.setDiscardMessages(false); } else if (check(checksIdent_)) { auto optPred = parsePredicateFlow(); if (optPred && check(TokenKind::equalgreater)) { // Done parsing the return type and predicate. predicate = *optPred; // Successful parse, show any messages that the lexer emitted. collect.setDiscardMessages(false); } else { savePoint.restore(); } } else { savePoint.restore(); } } else { savePoint.restore(); } } } #endif #if HERMES_PARSE_TS if (context_.getParseTS()) { // Separate logic for TS parsing here, because the semantics don't // require as much complexity as Flow due to a lack of predicates. if (allowTypedArrowFunction == AllowTypedArrowFunction::Yes && ((*state.optLeftExpr)->getParens() != 0 || isa<ESTree::CoverEmptyArgsNode>(*state.optLeftExpr)) && check(TokenKind::colon)) { JSLexer::SavePoint savePoint{&lexer_}; // Defer our decision on whether to show or suppress messages for this // next section. // If we are unsuccessful during the parse, it can mean that we need to // start parsing JSX children inside tags, instead of function type // parameters. We need to suppress lexer messages because the lexing // rules inside JSX are quite different from TS. For example: x ? (1) : // <tag>#{foo}</tag>; // ^ // and // x ? (1) : <tag>"</tag>; // ^ // must be able to handle the lexer errors that would occur if we lexed // the inside of the JSX tags as JS. CollectMessagesRAII collect{&sm_, true}; SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotationTS(annotStart); if (optType) returnType = *optType; if (optType) { if (check(TokenKind::equalgreater)) { // Done parsing the return type. // Successful parse, show any messages that the lexer emitted. collect.setDiscardMessages(false); } else { savePoint.restore(); } } else { savePoint.restore(); } } } #endif // Check for ArrowFunction. // ArrowFunction : ArrowParameters [no line terminator] => ConciseBody. // AsyncArrowFunction : // async [no line terminator] ArrowParameters [no line terminator] => // ConciseBody. if (check(TokenKind::equalgreater) && !lexer_.isNewLineBeforeCurrentToken()) { return parseArrowFunctionExpression( param, *state.optLeftExpr, typeParams, returnType, predicate, typeParams ? typeParams->getStartLoc() : startLoc, allowTypedArrowFunction, forceAsync); } #if HERMES_PARSE_FLOW if (typeParams) { errorExpected( TokenKind::equalgreater, "in generic arrow function", "start of function", typeParams->getStartLoc()); return None; } #endif if (!checkAssign()) return *state.optLeftExpr; // Check for destructuring assignment. if (check(TokenKind::equal) && (isa<ESTree::ArrayExpressionNode>(*state.optLeftExpr) || isa<ESTree::ObjectExpressionNode>(*state.optLeftExpr))) { state.optLeftExpr = reparseAssignmentPattern(*state.optLeftExpr, false); if (!state.optLeftExpr) return None; } state.op = getTokenIdent(tok_->getKind()); state.debugLoc = advance().Start; return nullptr; }; llvh::SmallVector<State, 2> stack; stack.emplace_back(); auto optRes = parseHelper( stack.back(), param, allowTypedArrowFunction, coverTypedParameters, typeParams); for (;;) { if (!optRes) return None; if (!stack.back().op) { stack.pop_back(); break; } if (stack.size() > ESTree::MAX_NESTED_ASSIGNMENTS) { recursionDepthExceeded(); return None; } stack.emplace_back(); optRes = parseHelper( stack.back(), param, AllowTypedArrowFunction::Yes, CoverTypedParameters::No, nullptr); } assert(optRes.getValue() != nullptr); while (!stack.empty()) { if (!checkEndAssignmentExpression()) { // Note: We don't assert the valid end of an AssignmentExpression here // because we do not know yet whether the entire file is well-formed. // This check errors here to ensure that we still catch missing elements // in `checkEndAssignmentExpression` while allowing us to avoid actually // asserting and crashing. error( tok_->getStartLoc(), "unexpected token after assignment expression"); return None; } auto &top = stack.back(); optRes = setLocation( top.leftStartLoc, getPrevTokenEndLoc(), top.debugLoc, new (context_) ESTree::AssignmentExpressionNode( top.op, top.optLeftExpr.getValue(), optRes.getValue())); stack.pop_back(); } return optRes.getValue(); } Optional<ESTree::Node *> JSParserImpl::parseExpression( Param param, CoverTypedParameters coverTypedParameters) { SMLoc startLoc = tok_->getStartLoc(); auto optExpr = parseAssignmentExpression( param, AllowTypedArrowFunction::Yes, coverTypedParameters, nullptr); if (!optExpr) return None; if (!check(TokenKind::comma)) return optExpr.getValue(); ESTree::NodeList exprList; exprList.push_back(*optExpr.getValue()); while (check(TokenKind::comma)) { // Eat the ",". auto commaRng = advance(); // CoverParenthesizedExpressionAndArrowParameterList: (Expression ,) if (check(TokenKind::r_paren)) { auto *coverNode = setLocation( commaRng, tok_->getStartLoc(), new (context_) ESTree::CoverTrailingCommaNode()); exprList.push_back(*coverNode); break; } ESTree::Node *expr2; if (check(TokenKind::dotdotdot)) { auto optRest = parseBindingRestElement(param); if (!optRest) return None; expr2 = setLocation( *optRest, *optRest, new (context_) ESTree::CoverRestElementNode(*optRest)); } else { auto optExpr2 = parseAssignmentExpression(param); if (!optExpr2) return None; expr2 = *optExpr2; } exprList.push_back(*expr2); } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::SequenceExpressionNode(std::move(exprList))); } Optional<ESTree::StringLiteralNode *> JSParserImpl::parseFromClause() { SMLoc startLoc = tok_->getStartLoc(); if (!checkAndEat(fromIdent_)) { error(startLoc, "'from' expected"); return None; } if (!need( TokenKind::string_literal, "after 'from'", "location of 'from'", startLoc)) { return None; } auto *source = setLocation( tok_, tok_, new (context_) ESTree::StringLiteralNode(tok_->getStringLiteral())); advance(); return source; } bool JSParserImpl::parseAssertClause(ESTree::NodeList &attributes) { assert(check(assertIdent_)); SMLoc start = advance().Start; // assert { } // assert { AssertEntries ,[opt] } // ^ if (!eat( TokenKind::l_brace, JSLexer::GrammarContext::AllowRegExp, "in import assertion", "start of assertion", start)) return false; while (!check(TokenKind::r_brace)) { // AssertionKey : StringLiteral // ^ ESTree::Node *key = nullptr; if (check(TokenKind::string_literal)) { key = setLocation( tok_, tok_, new (context_) ESTree::StringLiteralNode(tok_->getStringLiteral())); advance(); } else { if (!need( TokenKind::identifier, "in import assertion", "start of assertion", start)) return false; key = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getIdentifier(), nullptr, false)); advance(); } if (!eat( TokenKind::colon, JSLexer::GrammarContext::AllowRegExp, "in import assertion", "start of assertion", start)) return false; // AssertionKey : StringLiteral // ^ if (!need( TokenKind::string_literal, "in import assertion", "start of assertion", start)) return false; ESTree::Node *value = setLocation( tok_, tok_, new (context_) ESTree::StringLiteralNode(tok_->getStringLiteral())); advance(); attributes.push_back(*setLocation( key, value, new (context_) ESTree::ImportAttributeNode(key, value))); if (!checkAndEat(TokenKind::comma)) break; } // assert { AssertEntries ,[opt] } // ^ if (!eat( TokenKind::r_brace, JSLexer::GrammarContext::AllowRegExp, "in import assertion", "start of assertion", start)) return false; return true; } Optional<ESTree::ImportDeclarationNode *> JSParserImpl::parseImportDeclaration() { assert( check(TokenKind::rw_import) && "import declaration must start with 'import'"); SMLoc startLoc = advance().Start; if (check(TokenKind::string_literal)) { // import ModuleSpecifier ; // If the first token is a string literal, there are no specifiers, // so the import clause should not be parsed. auto *source = setLocation( tok_, tok_, new (context_) ESTree::StringLiteralNode(tok_->getStringLiteral())); advance(); ESTree::NodeList attributes{}; if (check(assertIdent_) && !lexer_.isNewLineBeforeCurrentToken()) { if (!parseAssertClause(attributes)) return None; } if (!eatSemi()) { return None; } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ImportDeclarationNode( {}, source, std::move(attributes), valueIdent_)); } ESTree::NodeList specifiers; auto optKind = parseImportClause(specifiers); if (!optKind) return None; UniqueString *kind = *optKind; auto optFromClause = parseFromClause(); if (!optFromClause) { return None; } ESTree::NodeList attributes{}; if (check(assertIdent_) && !lexer_.isNewLineBeforeCurrentToken()) { if (!parseAssertClause(attributes)) return None; } if (!eatSemi()) { return None; } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ImportDeclarationNode( std::move(specifiers), *optFromClause, std::move(attributes), kind)); } Optional<UniqueString *> JSParserImpl::parseImportClause( ESTree::NodeList &specifiers) { SMLoc startLoc = tok_->getStartLoc(); UniqueString *kind = valueIdent_; SMRange kindRange{}; #if HERMES_PARSE_FLOW if (context_.getParseFlow()) { if (checkN(typeIdent_, TokenKind::rw_typeof)) { kind = tok_->getResWordOrIdentifier(); kindRange = advance(); } } #endif #if HERMES_PARSE_TS if (context_.getParseTS()) { if (checkN(typeIdent_)) { kind = tok_->getResWordOrIdentifier(); kindRange = advance(); } } #endif if (check(TokenKind::identifier)) { if (check(fromIdent_) && kind == typeIdent_) { // Not actually a type import, just import default with the name 'type'. kind = valueIdent_; auto *defaultBinding = setLocation( kindRange, kindRange, new (context_) ESTree::IdentifierNode(typeIdent_, nullptr, false)); specifiers.push_back(*setLocation( defaultBinding, defaultBinding, new (context_) ESTree::ImportDefaultSpecifierNode(defaultBinding))); } else { // ImportedDefaultBinding // ImportedDefaultBinding , NameSpaceImport // ImportedDefaultBinding , NamedImports auto optDefaultBinding = parseBindingIdentifier(Param{}); if (!optDefaultBinding) { errorExpected( TokenKind::identifier, "in import clause", "start of import clause", startLoc); return None; } ESTree::IdentifierNode *defaultBinding = *optDefaultBinding; specifiers.push_back(*setLocation( defaultBinding, defaultBinding, new (context_) ESTree::ImportDefaultSpecifierNode(defaultBinding))); } if (!checkAndEat(TokenKind::comma)) { // If there was no comma, there's no more bindings to parse, // so return immediately. return kind; } } // At this point, either: // - the ImportedDefaultBinding was parsed and had a comma after it // - there was no ImportedDefaultBinding and we simply continue if (check(TokenKind::star)) { // NameSpaceImport auto optNsImport = parseNameSpaceImport(); if (!optNsImport) { return None; } specifiers.push_back(*optNsImport.getValue()); return kind; } // NamedImports is the only remaining possibility. if (!need( TokenKind::l_brace, "in import specifier clause", "location of import specifiers", startLoc)) { return kind; } if (!parseNamedImports(specifiers)) return None; return kind; } Optional<ESTree::Node *> JSParserImpl::parseNameSpaceImport() { assert(check(TokenKind::star) && "import namespace must start with *"); SMLoc startLoc = advance().Start; if (!checkAndEat(asIdent_)) { error(tok_->getStartLoc(), "'as' expected"); return None; } auto optLocal = parseBindingIdentifier(Param{}); if (!optLocal) { errorExpected( TokenKind::identifier, "in namespace import", "location of namespace import", startLoc); return None; } return setLocation( startLoc, *optLocal, new (context_) ESTree::ImportNamespaceSpecifierNode(*optLocal)); } bool JSParserImpl::parseNamedImports(ESTree::NodeList &specifiers) { assert(check(TokenKind::l_brace) && "named imports must start with {"); SMLoc startLoc = advance().Start; // BoundNames to check for duplicate entries in ImportDeclaration. // Values are the actual IdentifierNodes, used for error reporting. llvh::DenseMap<UniqueString *, ESTree::IdentifierNode *> boundNames{}; while (!check(TokenKind::r_brace)) { auto optSpecifier = parseImportSpecifier(startLoc); if (!optSpecifier) { return false; } // Check if the bound name was duplicated. ESTree::IdentifierNode *localIdent = cast<ESTree::IdentifierNode>(optSpecifier.getValue()->_local); auto insertRes = boundNames.try_emplace(localIdent->_name, localIdent); if (insertRes.second) { specifiers.push_back(*optSpecifier.getValue()); } else { // Report the error but continue parsing to see if there's any others. error( localIdent->getSourceRange(), "Duplicate entry in import declaration list"); sm_.note( insertRes.first->second->getSourceRange(), "first usage of name"); } if (!checkAndEat(TokenKind::comma)) { break; } } if (!eat( TokenKind::r_brace, JSLexer::AllowDiv, "at end of named imports", "location of '{'", startLoc)) { return false; } return true; } Optional<ESTree::ImportSpecifierNode *> JSParserImpl::parseImportSpecifier( SMLoc importLoc) { // ImportSpecifier: // ImportedBinding // IdentifierName as ImportedBinding SMLoc startLoc = tok_->getStartLoc(); UniqueString *kind = valueIdent_; ESTree::IdentifierNode *imported = nullptr; ESTree::IdentifierNode *local = nullptr; TokenKind localKind; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && checkAndEat(TokenKind::rw_typeof)) { kind = typeofIdent_; } #endif // This isn't wrapped in #if HERMES_PARSE_FLOW, as it is entangled // in the rest of the import specifier parsing code and doesn't actually // depend on JSParserImpl-flow specific code at all. if (HERMES_PARSE_FLOW && context_.getParseFlow() && check(typeIdent_) && kind == valueIdent_) { // Consume 'type', but make no assumptions about what it means yet. SMRange typeRange = advance(); if (check(TokenKind::r_brace, TokenKind::comma)) { // 'type' imported = setLocation( typeRange, typeRange, new (context_) ESTree::IdentifierNode(typeIdent_, nullptr, false)); local = imported; localKind = TokenKind::identifier; } else if (check(asIdent_)) { SMRange asRange = advance(); if (check(TokenKind::r_brace, TokenKind::comma)) { // 'type' 'as' kind = typeIdent_; imported = setLocation( asRange, asRange, new (context_) ESTree::IdentifierNode(asIdent_, nullptr, false)); local = imported; localKind = TokenKind::identifier; advance(); } else if (checkAndEat(asIdent_)) { // 'type' 'as' 'as' Identifier // ^ if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in import specifier", "specifiers start", importLoc); return None; } kind = typeIdent_; imported = setLocation( asRange, asRange, new (context_) ESTree::IdentifierNode(asIdent_, nullptr, false)); local = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); localKind = TokenKind::identifier; advance(); } else { // 'type' 'as' Identifier // ^ if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in import specifier", "specifiers start", importLoc); return None; } imported = setLocation( typeRange, typeRange, new (context_) ESTree::IdentifierNode(typeIdent_, nullptr, false)); local = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); localKind = TokenKind::identifier; advance(); } } else { // 'type' Identifier // ^ kind = typeIdent_; if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in import specifier", "specifiers start", importLoc); return None; } imported = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); local = imported; localKind = tok_->getKind(); advance(); if (checkAndEat(asIdent_)) { // type Identifier 'as' Identifier // ^ if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in import specifier", "specifiers start", importLoc); return None; } local = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); localKind = tok_->getKind(); advance(); } } } else { // Not attempting to parse a type identifier. if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in import specifier", "specifiers start", importLoc); return None; } imported = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); local = imported; localKind = tok_->getKind(); advance(); if (checkAndEat(asIdent_)) { if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in import specifier", "specifiers start", importLoc); return None; } local = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); localKind = tok_->getKind(); advance(); } } // Only the local name must be parsed as a binding identifier. // We need to check for 'as' before knowing what the local name is. // Thus, we need to validate the binding identifier for the local name // after the fact. if (!validateBindingIdentifier( Param{}, local->getSourceRange(), local->_name, localKind)) { error(local->getSourceRange(), "Invalid local name for import"); } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ImportSpecifierNode(imported, local, kind)); } Optional<ESTree::Node *> JSParserImpl::parseExportDeclaration() { assert( check(TokenKind::rw_export) && "parseExportDeclaration requires 'export'"); SMLoc startLoc = advance().Start; #if HERMES_PARSE_FLOW if (context_.getParseFlow() && check(typeIdent_)) { return parseExportTypeDeclarationFlow(startLoc); } #endif if (checkAndEat(TokenKind::star)) { // export * FromClause; // export * as IdentifierName FromClause; ESTree::Node *exportAs = nullptr; if (checkAndEat(asIdent_)) { // export * as IdentifierName FromClause; // ^ if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in export clause", "start of export", startLoc); return None; } exportAs = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); advance(); } auto optFromClause = parseFromClause(); if (!optFromClause) { return None; } if (!eatSemi()) { return None; } if (exportAs) { ESTree::NodeList specifiers{}; specifiers.push_back(*setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ExportNamespaceSpecifierNode(exportAs))); return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ExportNamedDeclarationNode( nullptr, std::move(specifiers), *optFromClause, valueIdent_)); } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ExportAllDeclarationNode(*optFromClause, valueIdent_)); } else if (checkAndEat(TokenKind::rw_default)) { // export default if (check(TokenKind::rw_function) || (check(asyncIdent_) && checkAsyncFunction())) { // export default HoistableDeclaration // Currently, the only hoistable declarations are functions. auto optFunDecl = parseFunctionDeclaration(ParamDefault); if (!optFunDecl) { return None; } return setLocation( startLoc, *optFunDecl, new (context_) ESTree::ExportDefaultDeclarationNode(*optFunDecl)); } else if (check(TokenKind::rw_class)) { auto optClassDecl = parseClassDeclaration(ParamDefault); if (!optClassDecl) { return None; } return setLocation( startLoc, *optClassDecl, new (context_) ESTree::ExportDefaultDeclarationNode(*optClassDecl)); #if HERMES_PARSE_FLOW } else if (context_.getParseFlow() && check(TokenKind::rw_enum)) { auto optEnum = parseEnumDeclarationFlow(); if (!optEnum) { return None; } return setLocation( startLoc, *optEnum, new (context_) ESTree::ExportDefaultDeclarationNode(*optEnum)); #endif } else { // export default AssignmentExpression ; auto optExpr = parseAssignmentExpression(ParamIn); if (!optExpr) { return None; } if (!eatSemi()) { return None; } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ExportDefaultDeclarationNode(*optExpr)); } } else if (check(TokenKind::l_brace)) { // export ExportClause FromClause ; // export ExportClause ; ESTree::NodeList specifiers{}; llvh::SmallVector<SMRange, 2> invalids{}; auto optExportClause = parseExportClause(specifiers, invalids); if (!optExportClause) { return None; } ESTree::Node *source = nullptr; if (check(fromIdent_)) { // export ExportClause FromClause ; auto optFromClause = parseFromClause(); if (!optFromClause) { return None; } source = *optFromClause; } else { // export ExportClause ; // ES9.0 15.2.3.1 // When there is no FromClause, any ranges added to invalids are actually // invalid, and should be reported as errors. for (const SMRange &range : invalids) { error(range, "Invalid exported name"); } } if (!eatSemi()) { return None; } return setLocation( startLoc, getPrevTokenEndLoc(), new (context_) ESTree::ExportNamedDeclarationNode( nullptr, std::move(specifiers), source, valueIdent_)); } else if (check(TokenKind::rw_var)) { // export VariableStatement auto optVar = parseVariableStatement(Param{}); if (!optVar) { return None; } return setLocation( startLoc, *optVar, new (context_) ESTree::ExportNamedDeclarationNode( *optVar, {}, nullptr, valueIdent_)); } // export Declaration [~Yield] if (!checkDeclaration()) { error(tok_->getSourceRange(), "expected declaration in export"); return None; } auto optDecl = parseDeclaration(Param{}); if (!optDecl) { return None; } ESTree::Node *decl = *optDecl; UniqueString *kind = valueIdent_; #if HERMES_PARSE_FLOW if (isa<ESTree::TypeAliasNode>(decl) || isa<ESTree::OpaqueTypeNode>(decl) || isa<ESTree::DeclareTypeAliasNode>(decl) || isa<ESTree::InterfaceDeclarationNode>(decl)) { kind = typeIdent_; } #endif return setLocation( startLoc, decl, new (context_) ESTree::ExportNamedDeclarationNode(decl, {}, nullptr, kind)); } bool JSParserImpl::parseExportClause( ESTree::NodeList &specifiers, llvh::SmallVectorImpl<SMRange> &invalids) { // ExportClause: // { } // { ExportsList } // { ExportsList , '} assert(check(TokenKind::l_brace) && "ExportClause requires '{'"); SMLoc startLoc = advance().Start; while (!check(TokenKind::r_brace)) { // Read all the elements of the ExportsList. auto optSpecifier = parseExportSpecifier(startLoc, invalids); if (!optSpecifier) { return false; } specifiers.push_back(*optSpecifier.getValue()); if (!checkAndEat(TokenKind::comma)) { break; } } return eat( TokenKind::r_brace, JSLexer::AllowDiv, "at end of export clause", "location of export", startLoc); } Optional<ESTree::Node *> JSParserImpl::parseExportSpecifier( SMLoc exportLoc, llvh::SmallVectorImpl<SMRange> &invalids) { // ExportSpecifier: // IdentifierName // IdentifierName as IdentifierName if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in export clause", "location of export clause", exportLoc); return None; } // ES9.0 15.2.3.1 Early errors for ReferencedBindings in ExportClause. // Add potentially error-raising identifier ranges to the invalids list here, // and the owner of the invalids list will report the ranges as errors if // necessary. if (tok_->isResWord() || check(implementsIdent_) || check(interfaceIdent_) || check(letIdent_) || check(packageIdent_) || check(privateIdent_) || check(protectedIdent_) || check(publicIdent_) || check(staticIdent_)) { invalids.push_back(tok_->getSourceRange()); } auto *local = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); advance(); ESTree::Node *exported; if (checkAndEat(asIdent_)) { // IdentifierName as IdentifierName if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in export clause", "location of export clause", exportLoc); return None; } exported = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); advance(); } else { // IdentifierName exported = local; } return setLocation( local, exported, new (context_) ESTree::ExportSpecifierNode(exported, local)); } ESTree::ExpressionStatementNode *JSParserImpl::parseDirective() { // Is the current token a directive? if (!lexer_.isCurrentTokenADirective()) return nullptr; // Allocate a StringLiteralNode for the directive. auto *strLit = setLocation( tok_, tok_, new (context_) ESTree::StringLiteralNode(tok_->getStringLiteral())); auto endLoc = tok_->getEndLoc(); // Actually process the directive. Note that we want to do that before we // have consumed any more tokens - strictness can affect the interpretation // of tokens. processDirective(strLit->_value); advance(JSLexer::AllowDiv); // Consume the optional semicolon. if (check(TokenKind::semi)) endLoc = advance().End; // Allocate an ExpressionStatementNode for the directive. return setLocation( strLit, endLoc, new (context_) ESTree::ExpressionStatementNode(strLit, strLit->_value)); } namespace { /// Upcast an Optional node type to a generic NodePtr, e.g. /// \p Optional<FunctionExpressionNode> to \p Optional<NodePtr>. template <typename T> Optional<ESTree::NodePtr> castNode(Optional<T> node) { if (!node.hasValue()) return None; return Optional<ESTree::NodePtr>(node.getValue()); } } // namespace bool JSParserImpl::preParseBuffer( Context &context, uint32_t bufferId, bool &useStaticBuiltinDetected) { PerfSection preparsing("Pre-Parsing JavaScript"); AllocationScope scope(context.getAllocator()); JSParserImpl parser(context, bufferId, PreParse); auto result = parser.parse(); useStaticBuiltinDetected = parser.getUseStaticBuiltin(); return result.hasValue(); } Optional<ESTree::NodePtr> JSParserImpl::parseLazyFunction( ESTree::NodeKind kind, bool paramYield, bool paramAwait, SMLoc start) { seek(start); paramYield_ = paramYield; paramAwait_ = paramAwait; switch (kind) { case ESTree::NodeKind::FunctionExpression: return castNode(parseFunctionExpression(true)); case ESTree::NodeKind::FunctionDeclaration: return castNode(parseFunctionDeclaration(ParamReturn, true)); case ESTree::NodeKind::Property: { auto node = parsePropertyAssignment(true); assert(node && "Reparsing of property assignment failed"); if (auto *prop = dyn_cast<ESTree::PropertyNode>(*node)) { return prop->_value; } else { // This isn't technically (llvm_)unreachable, since it'll happen if you // fudge the source buffer during execution. Assert and fail instead. assert(false && "Expected a getter/setter function"); return None; } } default: llvm_unreachable("Asked to parse unexpected node type"); } } } // namespace detail } // namespace parser } // namespace hermes