Optional JSParserImpl::parseClassElement()

in lib/Parser/JSParserImpl.cpp [4575:4935]


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));
}