private static function convertChildrenExpression()

in src/Migrations/AddXHPChildrenDeclarationMethodMigration.hack [273:380]


  private static function convertChildrenExpression(
    Node $in,
  ): FunctionCallExpression {
    if ($in is XHPChildrenParenthesizedList) {
      $count = $in->getXhpChildren()->getCount();
      invariant($count >= 1, 'Got empty XHP children parenthesized list');
      if ($count === 1) {
        return self::convertChildrenExpression(
          C\onlyx($in->getXhpChildren()->getChildrenOfItems()),
        );
      }
      $children = Vec\map(
        $in->getXhpChildren()->getChildrenOfItems(),
        $node ==> self::convertChildrenExpression($node),
      );
      return self::makeCall('sequence', null, $children);
    }

    if ($in is PostfixUnaryExpression) {
      $op = $in->getOperator();
      if ($op is PlusToken) {
        $fun = 'atLeastOneOf';
      } else if ($op is QuestionToken) {
        $fun = 'optional';
      } else if ($op is StarToken) {
        $fun = 'anyNumberOf';
      } else {
        invariant_violation(
          "Got an XHP postfix unary expression with unexpected operator '%s'",
          $in->getOperator()->getText(),
        );
      }
      return self::makeCall(
        $fun,
        null,
        vec[self::convertChildrenExpression($in->getOperand())],
      );
    }

    if ($in is BinaryExpression) {
      // foo | bar | baz
      invariant(
        $in->getOperator() is BarToken,
        "Got an XHP child binary expression with unexpected operator '%s'",
        $in->getOperator()->getText(),
      );
      // `foo | bar | baz` is `((foo | bar) | baz)` in the AST; flatten them out
      // for readability
      $next = $in;
      $parts = vec[];
      while ($next is BinaryExpression && $next->getOperator() is BarToken) {
        $parts[] = $next->getRightOperand();
        $next = $next->getLeftOperand();
      }
      $parts[] = $next;
      $parts = Vec\reverse($parts)
        |> Vec\map($$, $part ==> self::convertChildrenExpression($part));
      return self::makeCall('anyOf', null, $parts);
    }

    if ($in is NameExpression || $in is EmptyToken || $in is NameToken) {
      $name = $in is NameExpression ? $in->getWrappedNode() : $in;
      if (
        $name is NameToken || $name is EmptyToken || $name is XHPClassNameToken
      ) {
        $name = $name->getText();
        if ($name === 'pcdata' || $name === 'empty' || $name === 'any') {
          return self::makeCall($name);
        }

        // Class name
        return self::makeCall(
          'ofType',
          new TypeArguments(
            new LessThanToken(null, null),
            new NodeList(vec[
              new ListItem(
                new SimpleTypeSpecifier(new NameToken(null, null, $name)),
                null,
              ),
            ]),
            new GreaterThanToken(null, null),
          ),
        );
      }

      if ($name is XHPCategoryNameToken) {
        return self::makeCall('category', null, vec[new LiteralExpression(
          new SingleQuotedStringLiteralToken(
            null,
            null,
            "'".$name->getText()."'",
          ),
        )]);
      }

      invariant_violation(
        'Unhandled XHP child name type: %s',
        \get_class($name),
      );
    }

    invariant_violation(
      'Unhandled XHP children expression: %s (%s)',
      \get_class($in),
      $in->getCode(),
    );
  }