lib/Parser/JSParserImpl-ts.cpp (1,165 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 { #if HERMES_PARSE_TS Optional<ESTree::Node *> JSParserImpl::parseTypeAnnotationTS( Optional<SMLoc> wrappedStart) { llvh::SaveAndRestore<bool> saveParam(allowAnonFunctionType_, true); SMLoc start = tok_->getStartLoc(); ESTree::Node *result = nullptr; if (check(TokenKind::identifier)) { // Need to check if this is a predicate, which requires backtracking. JSLexer::SavePoint savePoint{&lexer_}; ESTree::Node *id = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getIdentifier(), nullptr, false)); advance(JSLexer::GrammarContext::Type); if (check(isIdent_)) { SMLoc wrappedStart = advance(JSLexer::GrammarContext::Type).Start; CHECK_RECURSION; auto optType = parseTypeAnnotationTS(wrappedStart); if (!optType) return None; result = setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSTypePredicateNode(id, *optType)); } else { savePoint.restore(); } } if (!result) { if (check(TokenKind::rw_new)) { advance(JSLexer::GrammarContext::Type); ESTree::Node *typeParams = nullptr; if (check(TokenKind::less)) { auto optTypeParams = parseTSTypeParameters(); if (!optTypeParams) return None; typeParams = *optTypeParams; } if (!need( TokenKind::l_paren, "in constructor type", "start of type", start)) return None; auto optResult = parseTSFunctionOrParenthesizedType( start, typeParams, IsConstructorType::Yes); if (!optResult) return None; result = *optResult; } else if (check(TokenKind::less)) { auto optTypeParams = parseTSTypeParameters(); if (!optTypeParams) return None; if (!need(TokenKind::l_paren, "in function type", "start of type", start)) return None; auto optResult = parseTSFunctionOrParenthesizedType( start, *optTypeParams, IsConstructorType::No); if (!optResult) return None; result = *optResult; } else { auto optResult = parseTSUnionType(); if (!optResult) return None; result = *optResult; } } if (checkAndEat(TokenKind::rw_extends, JSLexer::GrammarContext::Type)) { // Parse a conditional type. auto optCheck = parseTypeAnnotationTS(); if (!optCheck) return None; if (!eat( TokenKind::question, JSLexer::GrammarContext::Type, "in conditional type", "start of type", start)) return None; auto optTrue = parseTypeAnnotationTS(); if (!optTrue) return None; if (!eat( TokenKind::colon, JSLexer::GrammarContext::Type, "in conditional type", "start of type", start)) return None; auto optFalse = parseTypeAnnotationTS(); if (!optFalse) return None; result = setLocation( result, getPrevTokenEndLoc(), new (context_) ESTree::TSConditionalTypeNode( result, *optCheck, *optTrue, *optFalse)); } if (wrappedStart) return setLocation( *wrappedStart, result, new (context_) ESTree::TSTypeAnnotationNode(result)); return result; } Optional<ESTree::Node *> JSParserImpl::parseTSUnionType() { SMLoc start = tok_->getStartLoc(); checkAndEat(TokenKind::pipe, JSLexer::GrammarContext::Type); auto optFirst = parseTSIntersectionType(); if (!optFirst) return None; if (!check(TokenKind::pipe)) { // Done with the union, move on. return *optFirst; } ESTree::NodeList types{}; types.push_back(**optFirst); while (checkAndEat(TokenKind::pipe, JSLexer::GrammarContext::Type)) { auto optInt = parseTSIntersectionType(); if (!optInt) return None; types.push_back(**optInt); } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSUnionTypeNode(std::move(types))); } Optional<ESTree::Node *> JSParserImpl::parseTSIntersectionType() { SMLoc start = tok_->getStartLoc(); checkAndEat(TokenKind::amp, JSLexer::GrammarContext::Type); auto optFirst = parseTSPostfixType(); if (!optFirst) return None; if (!check(TokenKind::amp)) { // Done with the union, move on. return *optFirst; } ESTree::NodeList types{}; types.push_back(**optFirst); while (checkAndEat(TokenKind::amp, JSLexer::GrammarContext::Type)) { auto optInt = parseTSPostfixType(); if (!optInt) return None; types.push_back(**optInt); } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSIntersectionTypeNode(std::move(types))); } Optional<ESTree::Node *> JSParserImpl::parseTSTupleType() { assert(check(TokenKind::l_square)); SMLoc start = advance(JSLexer::GrammarContext::Type).Start; ESTree::NodeList types{}; while (!check(TokenKind::r_square)) { auto optType = parseTypeAnnotationTS(); if (!optType) return None; types.push_back(**optType); if (!checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type)) break; } if (!need( TokenKind::r_square, "at end of tuple type annotation", "start of tuple", start)) return None; return setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::TSTupleTypeNode(std::move(types))); } Optional<ESTree::Node *> JSParserImpl::parseTSFunctionOrParenthesizedType( SMLoc start, ESTree::Node *typeParams, IsConstructorType isConstructorType) { assert(check(TokenKind::l_paren)); // This is either // ( Type ) // ^ // or // ( ParamList ) => Type // ^ // so we use a similar approach to arrow function parameters by keeping track // and reparsing in certain cases. advance(JSLexer::GrammarContext::Type); bool isFunction = typeParams != nullptr; bool hasRest = false; ESTree::Node *type = nullptr; ESTree::NodeList params{}; if (allowAnonFunctionType_ && checkAndEat(TokenKind::dotdotdot, JSLexer::GrammarContext::Type)) { isFunction = true; hasRest = true; // Must be parameters, and this must be the last one. auto optName = parseTSFunctionTypeParam(); if (!optName) return None; params.push_back(*setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::RestElementNode(*optName))); } else if (check(TokenKind::l_paren)) { auto optType = parseTypeAnnotationTS(); if (!optType) return None; type = *optType; } else if (check(TokenKind::r_paren)) { isFunction = true; // ( ) // ^ // No parameters, but this must be an empty param list. } else { // Not sure yet whether this is a param or simply a type. auto optParam = parseTSFunctionTypeParam(); if (!optParam) return None; if (auto *param = llvh::dyn_cast<ESTree::TSParameterPropertyNode>(*optParam)) { if (param && (param->_accessibility || param->_export || param->_readonly || param->_static)) { // Must be a param. isFunction = true; } params.push_back(*param); } else if ( auto *ident = llvh::dyn_cast<ESTree::IdentifierNode>(*optParam)) { params.push_back(*ident); type = ident->_typeAnnotation ? ident->_typeAnnotation : reparseIdentifierAsTSTypeAnnotation(ident); if (ident->_typeAnnotation || ident->_optional) { // Must be a param. isFunction = true; } } else { type = *optParam; params.push_back(**optParam); } } // If isFunction was already forced by something previously then we // have no choice but to attempt to parse as a function type annotation. if ((isFunction || allowAnonFunctionType_) && checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type)) { isFunction = true; while (!check(TokenKind::r_paren)) { bool isRest = !hasRest && checkAndEat(TokenKind::dotdotdot, JSLexer::GrammarContext::Type); auto optParam = parseTSFunctionTypeParam(); if (!optParam) return None; if (isRest) { params.push_back(*setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::RestElementNode(*optParam))); checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type); break; } else { params.push_back(**optParam); } if (!checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type)) break; } } if (!eat( TokenKind::r_paren, JSLexer::GrammarContext::Type, "at end of function type parameters", "start of parameters", start)) return None; if (isFunction) { if (!eat( TokenKind::equalgreater, JSLexer::GrammarContext::Type, "in function type", "start of function", start)) return None; } else if (allowAnonFunctionType_) { if (checkAndEat(TokenKind::equalgreater, JSLexer::GrammarContext::Type)) { isFunction = true; } } if (!isFunction) { type->incParens(); return type; } auto optReturnType = parseTypeAnnotationTS(); if (!optReturnType) return None; if (isConstructorType == IsConstructorType::Yes) { return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSConstructorTypeNode( std::move(params), *optReturnType, typeParams)); } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSFunctionTypeNode( std::move(params), *optReturnType, typeParams)); } Optional<ESTree::Node *> JSParserImpl::parseTSFunctionTypeWithParams( SMLoc start, ESTree::NodeList &&params, ESTree::Node *typeParams) { assert(check(TokenKind::equalgreater)); advance(JSLexer::GrammarContext::Type); auto optReturnType = parseTypeAnnotationTS(); if (!optReturnType) return None; return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSFunctionTypeNode( std::move(params), *optReturnType, typeParams)); } bool JSParserImpl::parseTSFunctionTypeParams( SMLoc start, ESTree::NodeList &params) { assert(check(TokenKind::l_paren)); advance(JSLexer::GrammarContext::Type); while (!check(TokenKind::r_paren)) { auto optParam = parseTSFunctionTypeParam(); if (!optParam) return false; params.push_back(**optParam); if (!checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type)) break; } if (!eat( TokenKind::r_paren, JSLexer::GrammarContext::Type, "at end of function type parameters", "start of parameters", start)) return false; return true; } Optional<ESTree::Node *> JSParserImpl::parseTSFunctionTypeParam() { SMLoc start = tok_->getStartLoc(); ESTree::NodeLabel accessibilityNode = nullptr; bool readonlyNode = false; bool staticNode = false; bool exportNode = false; while (checkN( TokenKind::identifier, TokenKind::rw_static, TokenKind::rw_export)) { // Check if this is a modifier. if (!staticNode && checkN(TokenKind::rw_static, staticIdent_)) { advance(JSLexer::GrammarContext::Type); if (checkN( TokenKind::identifier, TokenKind::rw_static, TokenKind::rw_export)) { staticNode = true; continue; } } if (!exportNode && checkN(TokenKind::rw_export)) { advance(JSLexer::GrammarContext::Type); if (checkN( TokenKind::identifier, TokenKind::rw_static, TokenKind::rw_export)) { exportNode = true; continue; } } if (!readonlyNode && checkN(readonlyIdent_)) { advance(JSLexer::GrammarContext::Type); if (checkN( TokenKind::identifier, TokenKind::rw_static, TokenKind::rw_export)) { readonlyNode = true; continue; } } if (!accessibilityNode) { if (checkN(TokenKind::rw_public, publicIdent_)) { advance(JSLexer::GrammarContext::Type); if (checkN( TokenKind::identifier, TokenKind::rw_static, TokenKind::rw_export)) { accessibilityNode = publicIdent_; continue; } } if (checkN(TokenKind::rw_private, privateIdent_)) { advance(JSLexer::GrammarContext::Type); if (checkN( TokenKind::identifier, TokenKind::rw_static, TokenKind::rw_export)) { accessibilityNode = privateIdent_; continue; } } if (checkN(TokenKind::rw_protected, protectedIdent_)) { advance(JSLexer::GrammarContext::Type); if (checkN( TokenKind::identifier, TokenKind::rw_static, TokenKind::rw_export)) { accessibilityNode = protectedIdent_; continue; } } } // Not a modifier. break; } auto optParam = parseBindingElement(Param{}); if (!optParam) return None; if (accessibilityNode || readonlyNode || staticNode || exportNode) { return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSParameterPropertyNode( *optParam, accessibilityNode, readonlyNode, staticNode, exportNode)); } return *optParam; } Optional<ESTree::Node *> JSParserImpl::parseTSDeclaration() { assert(checkDeclaration()); SMLoc start = tok_->getStartLoc(); if (checkN(TokenKind::rw_interface, interfaceIdent_)) { return parseTSInterfaceDeclaration(); } if (checkAndEat(typeIdent_, JSLexer::GrammarContext::Type)) { return parseTSTypeAliasDeclaration(start); } if (check(namespaceIdent_)) { return parseTSNamespaceDeclaration(); } assert(check(TokenKind::rw_enum)); return parseTSEnumDeclaration(); } Optional<ESTree::Node *> JSParserImpl::parseTSTypeAliasDeclaration( SMLoc start) { if (!need( TokenKind::identifier, "in type alias", "start of type alias", start)) return None; ESTree::Node *id = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getIdentifier(), nullptr, false)); advance(JSLexer::GrammarContext::Type); ESTree::Node *typeParams = nullptr; if (check(TokenKind::less)) { auto optTypeParams = parseTSTypeParameters(); if (!optTypeParams) return None; typeParams = *optTypeParams; } if (!eat( TokenKind::equal, JSLexer::GrammarContext::Type, "in type alias", "start of type alias", start)) return None; auto optRight = parseTypeAnnotationTS(); if (!optRight) return None; ESTree::Node *right = *optRight; if (!eatSemi(true)) return None; return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSTypeAliasDeclarationNode(id, typeParams, right)); } Optional<ESTree::Node *> JSParserImpl::parseTSInterfaceDeclaration() { assert(checkN(TokenKind::rw_interface, interfaceIdent_)); SMLoc start = advance(JSLexer::GrammarContext::Type).Start; if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in interface declaration", "start of interface", start); return None; } ESTree::Node *id = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); advance(JSLexer::GrammarContext::Type); ESTree::Node *typeParams = nullptr; if (check(TokenKind::less)) { auto optTypeParams = parseTSTypeParameters(); if (!optTypeParams) return None; typeParams = *optTypeParams; } ESTree::NodeList extends{}; if (checkAndEat( TokenKind::rw_extends, JSLexer::GrammarContext::AllowRegExp)) { do { auto optExpr = parseTSTypeReference(); if (!optExpr) return None; ESTree::TSTypeReferenceNode *expr = *optExpr; ESTree::Node *typeArgs = nullptr; if (expr->_typeParameters) { typeArgs = expr->_typeParameters; expr->_typeParameters = nullptr; } extends.push_back(*setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSInterfaceHeritageNode(expr, typeArgs))); if (!checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type)) break; } while (!check(TokenKind::l_brace)); } SMLoc bodyStart = tok_->getStartLoc(); if (!eat( TokenKind::l_brace, JSLexer::GrammarContext::Type, "in interface declaration", "start of interface", start)) return None; ESTree::NodeList members{}; while (!check(TokenKind::r_brace)) { auto optMember = parseTSObjectTypeMember(); if (!optMember) return None; members.push_back(**optMember); bool hasNext = check(TokenKind::comma, TokenKind::semi); if (hasNext) { advance(JSLexer::GrammarContext::Type); } else { break; } } if (!eat( TokenKind::r_brace, JSLexer::GrammarContext::Type, "at end of object type", "start of object type", start)) return None; ESTree::Node *body = setLocation( bodyStart, getPrevTokenEndLoc(), new (context_) ESTree::TSInterfaceBodyNode(std::move(members))); return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSInterfaceDeclarationNode( id, body, std::move(extends), typeParams)); } Optional<ESTree::Node *> JSParserImpl::parseTSEnumDeclaration() { assert(check(TokenKind::rw_enum)); SMLoc start = advance(JSLexer::GrammarContext::Type).Start; auto optName = parseBindingIdentifier(Param{}); if (!optName) { errorExpected( TokenKind::identifier, "in enum declaration", "start of enum", start); return None; } ESTree::Node *name = *optName; if (!eat( TokenKind::l_brace, JSLexer::GrammarContext::Type, "in enum declaration", "start of enum", start)) return None; ESTree::NodeList members{}; while (!check(TokenKind::r_brace)) { auto optMember = parseTSEnumMember(); if (!optMember) return None; members.push_back(**optMember); if (!checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type)) break; } if (!eat( TokenKind::r_brace, JSLexer::GrammarContext::Type, "in enum declaration", "start of enum", start)) return None; return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSEnumDeclarationNode(name, std::move(members))); } Optional<ESTree::Node *> JSParserImpl::parseTSEnumMember() { SMLoc start = tok_->getStartLoc(); auto optName = parseBindingIdentifier(Param{}); if (!optName) { errorExpected( TokenKind::identifier, "in enum member", "start of member", start); return None; } ESTree::Node *name = *optName; ESTree::Node *init = nullptr; if (checkAndEat(TokenKind::equal)) { auto optExpr = parseAssignmentExpression(); if (!optExpr) return None; init = *optExpr; } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSEnumMemberNode(name, init)); } Optional<ESTree::Node *> JSParserImpl::parseTSNamespaceDeclaration() { assert(check(namespaceIdent_)); SMLoc start = advance(JSLexer::GrammarContext::Type).Start; auto optName = parseTSQualifiedName(); if (!optName) { errorExpected( TokenKind::identifier, "in namespace declaration", "start of namespace", start); return None; } ESTree::Node *name = *optName; if (!eat( TokenKind::l_brace, JSLexer::GrammarContext::Type, "in namespace declaration", "start of namespace", start)) return None; ESTree::NodeList members{}; while (!check(TokenKind::r_brace)) { auto optMember = parseStatementListItem(Param{}, AllowImportExport::Yes, members); if (!optMember) return None; } if (!eat( TokenKind::r_brace, JSLexer::GrammarContext::Type, "in namespace declaration", "start of namespace", start)) return None; ESTree::Node *body = setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSModuleBlockNode(std::move(members))); return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSModuleMemberNode(name, body)); } Optional<ESTree::Node *> JSParserImpl::parseTSTypeParameters() { assert(check(TokenKind::less)); SMLoc start = advance(JSLexer::GrammarContext::Type).Start; ESTree::NodeList params{}; do { auto optType = parseTSTypeParameter(); if (!optType) return None; params.push_back(**optType); if (!checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type)) break; } while (!check(TokenKind::greater)); SMLoc end = tok_->getEndLoc(); if (!eat( TokenKind::greater, JSLexer::GrammarContext::Type, "at end of type parameters", "start of type parameters", start)) return None; return setLocation( start, end, new (context_) ESTree::TSTypeParameterDeclarationNode(std::move(params))); } Optional<ESTree::Node *> JSParserImpl::parseTSTypeParameter() { SMLoc start = tok_->getStartLoc(); if (!need(TokenKind::identifier, "in type parameter", nullptr, {})) return None; ESTree::IdentifierNode *name = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getIdentifier(), nullptr, false)); advance(JSLexer::GrammarContext::Type); ESTree::Node *constraint = nullptr; if (checkAndEat(TokenKind::rw_extends, JSLexer::GrammarContext::Type)) { auto optType = parseTypeAnnotationTS(); if (!optType) return None; constraint = *optType; } ESTree::Node *init = nullptr; if (checkAndEat(TokenKind::equal, JSLexer::GrammarContext::Type)) { auto optType = parseTypeAnnotationTS(); if (!optType) return None; init = *optType; } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSTypeParameterNode(name, constraint, init)); } Optional<ESTree::Node *> JSParserImpl::parseTSPostfixType() { SMLoc start = tok_->getStartLoc(); auto optPrimary = parseTSPrimaryType(); if (!optPrimary) return None; ESTree::Node *result = *optPrimary; // Parse any [] after the primary type. while (!lexer_.isNewLineBeforeCurrentToken() && checkAndEat(TokenKind::l_square, JSLexer::GrammarContext::Type)) { if (check(TokenKind::r_square)) { result = setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::TSArrayTypeNode(result)); } else { auto optType = parseTypeAnnotationTS(); if (!optType) return None; if (!eat( TokenKind::r_square, JSLexer::GrammarContext::Type, "in indexed access type", "start of type", start)) return None; result = setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSIndexedAccessTypeNode(result, *optType)); } } return result; } Optional<ESTree::Node *> JSParserImpl::parseTSPrimaryType() { CHECK_RECURSION; SMLoc start = tok_->getStartLoc(); switch (tok_->getKind()) { case TokenKind::star: return setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::ExistsTypeAnnotationNode()); case TokenKind::l_paren: return parseTSFunctionOrParenthesizedType( start, nullptr, IsConstructorType::No); case TokenKind::l_brace: return parseTSObjectType(); case TokenKind::rw_interface: return parseTSInterfaceDeclaration(); case TokenKind::rw_typeof: return parseTSTypeQuery(); case TokenKind::l_square: return parseTSTupleType(); case TokenKind::rw_this: return setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::TSThisTypeNode()); case TokenKind::rw_static: case TokenKind::identifier: if (tok_->getResWordOrIdentifier() == anyIdent_) { return setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::TSAnyKeywordNode()); } if (tok_->getResWordOrIdentifier() == booleanIdent_) { return setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::TSBooleanKeywordNode()); } if (tok_->getResWordOrIdentifier() == numberIdent_) { return setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::TSNumberKeywordNode()); } if (tok_->getResWordOrIdentifier() == symbolIdent_) { return setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::TSSymbolKeywordNode()); } if (tok_->getResWordOrIdentifier() == stringIdent_) { return setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::TSStringKeywordNode()); } { auto optRef = parseTSTypeReference(); if (!optRef) return None; return *optRef; } case TokenKind::rw_void: return setLocation( start, advance(JSLexer::GrammarContext::Type).End, new (context_) ESTree::TSVoidKeywordNode()); case TokenKind::string_literal: { UniqueString *str = tok_->getStringLiteral(); SMLoc end = advance(JSLexer::GrammarContext::Type).End; return setLocation( start, end, new (context_) ESTree::TSLiteralTypeNode(setLocation( start, end, new (context_) ESTree::StringLiteralNode(str)))); } case TokenKind::numeric_literal: { double str = tok_->getNumericLiteral(); SMLoc end = advance(JSLexer::GrammarContext::Type).End; return setLocation( start, end, new (context_) ESTree::TSLiteralTypeNode(setLocation( start, end, new (context_) ESTree::NumericLiteralNode(str)))); } default: if (tok_->isResWord()) { auto optRef = parseTSTypeReference(); if (!optRef) return None; return *optRef; } error(tok_->getStartLoc(), "unexpected token in type annotation"); return None; } } Optional<ESTree::TSTypeReferenceNode *> JSParserImpl::parseTSTypeReference() { assert(check(TokenKind::identifier) || tok_->isResWord()); SMLoc start = tok_->getStartLoc(); auto optName = parseTSQualifiedName(); if (!optName) return None; ESTree::Node *typeName = *optName; ESTree::Node *typeParams = nullptr; if (check(TokenKind::less)) { auto optTypeParams = parseTSTypeArguments(); if (!optTypeParams) return None; typeParams = *optTypeParams; } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSTypeReferenceNode(typeName, typeParams)); } Optional<ESTree::Node *> JSParserImpl::parseTSQualifiedName() { SMLoc start = tok_->getStartLoc(); ESTree::Node *typeName = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); advance(JSLexer::GrammarContext::Type); while (checkAndEat(TokenKind::period, JSLexer::GrammarContext::Type)) { if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in qualified type name", "start of type name", start); return None; } ESTree::Node *right = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); advance(JSLexer::GrammarContext::Type); typeName = setLocation( typeName, getPrevTokenEndLoc(), new (context_) ESTree::TSQualifiedNameNode(typeName, right)); } return typeName; } Optional<ESTree::Node *> JSParserImpl::parseTSTypeQuery() { assert(check(TokenKind::rw_typeof)); SMLoc start = advance(JSLexer::GrammarContext::Type).Start; if (!(tok_->isResWord() || check(TokenKind::identifier))) { errorExpected( TokenKind::identifier, "in type query", "start of type query", start); return None; } ESTree::Node *typeName = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); advance(JSLexer::GrammarContext::Type); while (checkAndEat(TokenKind::period, JSLexer::GrammarContext::Type)) { if (!check(TokenKind::identifier) && !tok_->isResWord()) { errorExpected( TokenKind::identifier, "in qualified type name", "start of type name", start); return None; } ESTree::Node *right = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode( tok_->getResWordOrIdentifier(), nullptr, false)); advance(JSLexer::GrammarContext::Type); typeName = setLocation( typeName, getPrevTokenEndLoc(), new (context_) ESTree::TSQualifiedNameNode(typeName, right)); } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSTypeQueryNode(typeName)); } Optional<ESTree::Node *> JSParserImpl::parseTSTypeArguments() { assert(check(TokenKind::less)); SMLoc start = advance(JSLexer::GrammarContext::Type).Start; ESTree::NodeList params{}; while (!check(TokenKind::greater)) { auto optType = parseTypeAnnotationTS(); if (!optType) return None; params.push_back(**optType); if (!checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type)) break; } SMLoc end = tok_->getEndLoc(); if (!eat( TokenKind::greater, JSLexer::GrammarContext::Type, "at end of type parameters", "start of type parameters", start)) return None; return setLocation( start, end, new (context_) ESTree::TSTypeParameterInstantiationNode(std::move(params))); } Optional<ESTree::Node *> JSParserImpl::parseTSObjectType() { assert(check(TokenKind::l_brace)); SMLoc start = advance(JSLexer::GrammarContext::Type).Start; ESTree::NodeList members{}; while (!check(TokenKind::r_brace)) { auto optMember = parseTSObjectTypeMember(); if (!optMember) return None; members.push_back(**optMember); bool hasNext = check(TokenKind::comma, TokenKind::semi); if (hasNext) { advance(JSLexer::GrammarContext::Type); } else { break; } } if (!eat( TokenKind::r_brace, JSLexer::GrammarContext::Type, "at end of object type", "start of object type", start)) return None; return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSTypeLiteralNode(std::move(members))); } Optional<ESTree::Node *> JSParserImpl::parseTSObjectTypeMember() { SMLoc start = tok_->getStartLoc(); if (check(TokenKind::l_paren)) { ESTree::NodeList params{}; if (!parseTSFunctionTypeParams(start, params)) return None; ESTree::Node *returnType = nullptr; if (checkAndEat(TokenKind::colon, JSLexer::GrammarContext::Type)) { auto optType = parseTypeAnnotationTS(); if (!optType) return None; returnType = *optType; } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSCallSignatureDeclarationNode( std::move(params), returnType)); } bool optional = false; bool computed = false; // TODO: Parse modifiers. bool readonly = false; bool isStatic = false; bool isExport = false; ESTree::Node *key = nullptr; // TODO: Parse initializer. ESTree::Node *init = nullptr; if (checkAndEat(TokenKind::l_square, JSLexer::GrammarContext::Type)) { computed = true; if (check(TokenKind::identifier)) { auto optNext = lexer_.lookahead1(llvh::None); if (optNext.hasValue() && *optNext == TokenKind::colon) { // Unambiguously an index signature. return parseTSIndexSignature(start); } } auto optExpr = parseAssignmentExpression(ParamIn); if (!optExpr) return None; key = *optExpr; if (!eat( TokenKind::r_square, JSLexer::GrammarContext::Type, "at end of computed property type", "start of property", start)) return None; if (checkAndEat(TokenKind::question, JSLexer::GrammarContext::Type)) { optional = true; } } else { if (!need(TokenKind::identifier, "in property", "start of property", start)) return None; key = setLocation( tok_, tok_, new (context_) ESTree::IdentifierNode(tok_->getIdentifier(), nullptr, false)); advance(JSLexer::GrammarContext::Type); if (checkAndEat(TokenKind::question, JSLexer::GrammarContext::Type)) { optional = true; } } if (check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotationTS(annotStart); if (!optType) return None; return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSPropertySignatureNode( key, *optType, init, optional, computed, readonly, isStatic, isExport)); } if (check(TokenKind::l_paren)) { ESTree::NodeList params{}; if (!parseTSFunctionTypeParams(start, params)) return None; ESTree::Node *returnType = nullptr; if (check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotationTS(annotStart); if (!optType) return None; returnType = *optType; } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSMethodSignatureNode( key, std::move(params), returnType, computed)); } ESTree::Node *returnType = nullptr; if (check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotationTS(annotStart); if (!optType) return None; returnType = *optType; } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSPropertySignatureNode( key, returnType, init, optional, computed, readonly, isStatic, isExport)); } Optional<ESTree::Node *> JSParserImpl::parseTSIndexSignature(SMLoc start) { ESTree::NodeList params{}; while (!check(TokenKind::r_square)) { auto optKey = parseBindingIdentifier(Param{}); if (!optKey) { errorExpected( TokenKind::identifier, "in property", "start of property", start); return None; } params.push_back(**optKey); if (!checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type)) break; } if (!eat( TokenKind::r_square, JSLexer::GrammarContext::Type, "at end of indexer type annotation", "start of indexer", start)) return None; ESTree::Node *returnType = nullptr; if (check(TokenKind::colon)) { SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start; auto optType = parseTypeAnnotationTS(annotStart); if (!optType) return None; returnType = *optType; } return setLocation( start, getPrevTokenEndLoc(), new (context_) ESTree::TSIndexSignatureNode(std::move(params), returnType)); } ESTree::Node *JSParserImpl::reparseIdentifierAsTSTypeAnnotation( ESTree::IdentifierNode *ident) { UniqueString *name = ident->_name; if (name == anyIdent_) { return setLocation(ident, ident, new (context_) ESTree::TSAnyKeywordNode()); } if (name == booleanIdent_) { return setLocation( ident, ident, new (context_) ESTree::TSBooleanKeywordNode()); } if (name == numberIdent_) { return setLocation( ident, ident, new (context_) ESTree::TSNumberKeywordNode()); } if (name == symbolIdent_) { return setLocation( ident, ident, new (context_) ESTree::TSSymbolKeywordNode()); } if (name == stringIdent_) { return setLocation( ident, ident, new (context_) ESTree::TSStringKeywordNode()); } return setLocation( ident, ident, new (context_) ESTree::TSTypeReferenceNode(ident, {})); } #endif } // namespace detail } // namespace parser } // namespace hermes