public function migrateFile()

in src/Migrations/HSLMigration.hack [156:289]


  public function migrateFile(string $path, Script $root): Script {
    // find all the function calls
    $nodes = $root->getDescendantsByType<FunctionCallExpression>();

    // keep track of any HSL namespaces we may have to add to the top of the file
    $found_namespaces = keyset[];

    // check if any functions are in the replacement list
    foreach ($nodes as $node) {
      // bail if not in the config
      $fn_name = $this->getFunctionName($node);
      if (
        $fn_name === null ||
        !C\contains_key(self::PHP_HSL_REPLACEMENTS, $fn_name)
      ) {
        continue;
      }

      // found a function to replace!
      $replace_config = self::PHP_HSL_REPLACEMENTS[$fn_name];
      $namespace = $replace_config['ns'];
      $replacement = $replace_config['name'];

      // build the replacement AST node
      $new_node = $this->replaceFunctionName(
        $node,
        $namespace.'\\'.$replacement,
      );

      // possibly change argument order
      $argument_order = $replace_config['argument_order'] ?? null;
      if (
        $argument_order !== null || ($replace_config['has_overrides'] ?? false)
      ) {
        /*HHAST_FIXME[DontUseAsioJoin]*/
        list($new_node, $found_namespaces) = \HH\Asio\join(
          $this->maybeMutateArgumentsAsync(
            $root,
            $new_node,
            $argument_order,
            $path,
            $found_namespaces,
          ),
        );
      }

      // if we got null back here, it means the function call has unsupported arguments. forget it for now
      if ($new_node === null) {
        continue;
      }

      // we know we're rewriting the node, so now we know we need the namespace
      $found_namespaces[] = $namespace;

      // replace it in the ast
      $root = $root->replace($node, $new_node);

      // potentially change adjacent expressions to check for null instead of false
      if ($replace_config['replace_false_with_null'] ?? false) {
        $root = $this->maybeChangeFalseToNull($root, $new_node);
      }
    }

    if (C\count($found_namespaces) === 0) {
      return $root;
    }

    // add "use namespace" declarations at the top if they aren't already present
    $declarations = $root->getDescendantsByType<INamespaceUseDeclaration>();
    list($hsl_declarations, $suffixes) = $this->findUseDeclarations(
      $declarations,
    );

    $count_before = C\count($suffixes);

    // add new suffixes to the current list of suffixes
    $suffixes = Keyset\union($suffixes, $found_namespaces);

    // added any new suffixes?
    if (C\count($suffixes) === $count_before) {
      return $root;
    }

    // remove any current use statements for HH\Lib\* namespaces, we'll group them together
    // Can't use ->getDescendantsByType<NodeList>() because NodeList is generic.
    $lists = $root->getDescendantsOfType(NodeList::class);
    foreach ($lists as $list) {
      $children = $list->toVec();
      $filtered = Vec\filter(
        $children,
        $c ==> !C\contains($hsl_declarations, $c),
      );
      if ($children !== $filtered) {
        $root = $root->replace($list, new NodeList($filtered));
      }
    }

    // build a possibly grouped namespace use declaration
    $new_namespace_use_declaration = $this->buildUseDeclaration($suffixes);

    // insert the new node: skip the <?hh sigil and namespace declaration if present,
    // then insert before the first declaration that remains
    foreach ($root->getChildren()['declarations']->getChildren() as $child) {
      if ($child is MarkupSection) {
        continue;
      }

      if ($child is NamespaceDeclaration) {
        $body = $child->getBody();
        // namespace Foo; style declaration, skip over it
        if ($body is NamespaceEmptyBody) {
          continue;
        }
        // namespace Foo { style declaration
        // insert the use statement inside the braces, before the first child
        invariant($body is NamespaceBody, 'expected NamespaceBody');
        $child = $body->getDeclarationsx()->getChildren() |> C\firstx($$);
      }

      if ($child is INamespaceUseDeclaration) {
        // next statement is another use declaration, remove the trailing newline
        $last_token = $new_namespace_use_declaration->getLastTokenx();
        $new_namespace_use_declaration = $new_namespace_use_declaration
          ->replace($last_token, $last_token->withTrailing(null));
      }
      $parent = $root->getParentOfDescendant($child) as NodeList<_>;
      return $root->replace(
        $parent,
        $parent->insertBefore($child, $new_namespace_use_declaration),
      );
    }

    invariant_violation('should not fail to insert new node');
  }